반응형

먼저 JWT의 기본 개념에 대해 설명드리겠습니다:

JWT는 당사자간에 정보를 JSON 객체로 안전하게 전송하기 위한 간단한 방법입니다. 이 정보는 디지털 서명되어 있어 신뢰할 수 있습니다.

JWT의 구조:

  1. Header: 토큰 유형과 사용된 서명 알고리즘
  2. Payload: 전달하려는 데이터(클레임)
  3. Signature: 헤더와 페이로드를 기반으로 한 서명

주요 사용 사례:

  • 인증(Authentication)
  • 정보 교환
  • 상태 유지(Stateless) 서버 구현

 

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;

public class JwtAuthenticationExample : System.Web.UI.Page
{
    private readonly string _secretKey = "your-256-bit-secret-key-here";
    
    // JWT 토큰 생성 메서드
    protected string GenerateJwtToken(string userId, string username, string role)
    {
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secretKey));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, userId),
            new Claim(JwtRegisteredClaimNames.UniqueName, username),
            new Claim(ClaimTypes.Role, role),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString())
        };

        var token = new JwtSecurityToken(
            issuer: "your-issuer",
            audience: "your-audience",
            claims: claims,
            expires: DateTime.UtcNow.AddHours(1),
            signingCredentials: credentials
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

    // JWT 토큰 검증 메서드
    protected bool ValidateToken(string token)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes(_secretKey);

        try
        {
            tokenHandler.ValidateToken(token, new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = true,
                ValidIssuer = "your-issuer",
                ValidateAudience = true,
                ValidAudience = "your-audience",
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero
            }, out SecurityToken validatedToken);

            return true;
        }
        catch
        {
            return false;
        }
    }

    // 로그인 처리 예제
    protected void LoginButton_Click(object sender, EventArgs e)
    {
        // 실제 구현에서는 데이터베이스에서 사용자 검증
        string userId = "123";
        string username = "testuser";
        string role = "user";

        string token = GenerateJwtToken(userId, username, role);
        
        // 토큰을 클라이언트에 전달 (예: 쿠키로 저장)
        Response.Cookies.Add(new HttpCookie("jwt_token", token));
    }

    // 보호된 리소스 접근 예제
    protected void Page_Load(object sender, EventArgs e)
    {
        HttpCookie tokenCookie = Request.Cookies["jwt_token"];
        
        if (tokenCookie != null && ValidateToken(tokenCookie.Value))
        {
            // 인증된 사용자 처리
            // 보호된 리소스에 접근 허용
        }
        else
        {
            // 인증되지 않은 사용자 처리
            Response.Redirect("~/Login.aspx");
        }
    }
}

// Web.config 설정
/*
<configuration>
  <system.web>
    <authentication mode="None" />
  </system.web>
  <system.webServer>
    <modules>
      <remove name="FormsAuthentication" />
    </modules>
  </system.webServer>
</configuration>
*/

 

 

이 예제 코드의 주요 특징:

  1. 토큰 생성 (GenerateJwtToken):
    • 사용자 ID, 이름, 역할 등의 클레임 포함
    • 토큰 만료 시간 설정 (1시간)
    • HMAC SHA256 알고리즘으로 서명
  2. 토큰 검증 (ValidateToken):
    • 토큰의 서명 검증
    • 발급자(issuer)와 수신자(audience) 검증
    • 만료 시간 검증
  3. 실제 사용 예시:
    • 로그인 시 토큰 생성
    • 보호된 페이지 접근 시 토큰 검증

3번의 로그인 버튼 쪽이랑 페이지 로드 부분은 별로 신경 안 쓰셔도 됩니다. 

보통 클라이언트에서 위에 두개의 함수를 이용하여 토큰을 생성하고 검증하는 부분으로 사용되기 때문입니다.

 

구현 시 주의사항:

  1. 시크릿 키는 안전하게 보관해야 합니다
  2. HTTPS를 사용하여 통신해야 합니다
  3. 토큰 만료 시간을 적절히 설정해야 합니다
  4. 필요한 패키지를 설치해야 합니다:

 

 

 

JWT를 활용한 클라이언트 구현을 자세히 만들어보겠습니다.

<!-- Login.aspx -->
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="JwtExample.Login" %>

<!DOCTYPE html>
<html>
<head>
    <title>로그인</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <style>
        .login-container {
            max-width: 400px;
            margin: 50px auto;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        .form-group label {
            display: block;
            margin-bottom: 5px;
        }
        .form-group input {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        .error-message {
            color: red;
            margin-top: 10px;
            display: none;
        }
    </style>
</head>
<body>
    <div class="login-container">
        <h2>로그인</h2>
        <div class="form-group">
            <label for="username">사용자명:</label>
            <input type="text" id="username" name="username" />
        </div>
        <div class="form-group">
            <label for="password">비밀번호:</label>
            <input type="password" id="password" name="password" />
        </div>
        <button onclick="login()">로그인</button>
        <div id="errorMessage" class="error-message"></div>
    </div>

    <script>
        function login() {
            const username = $('#username').val();
            const password = $('#password').val();

            // 로그인 요청
            $.ajax({
                url: 'Login.aspx/LoginUser',
                type: 'POST',
                contentType: 'application/json',
                data: JSON.stringify({
                    username: username,
                    password: password
                }),
                success: function(response) {
                    if (response.d.success) {
                        // JWT 토큰 저장
                        localStorage.setItem('jwt_token', response.d.token);
                        // 사용자 정보 저장
                        localStorage.setItem('user_info', JSON.stringify(response.d.userInfo));
                        // 메인 페이지로 리다이렉트
                        window.location.href = 'Dashboard.aspx';
                    } else {
                        $('#errorMessage').text(response.d.message).show();
                    }
                },
                error: function() {
                    $('#errorMessage').text('로그인 중 오류가 발생했습니다.').show();
                }
            });
        }
    </script>
</body>
</html>

<!-- Dashboard.aspx -->
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Dashboard.aspx.cs" Inherits="JwtExample.Dashboard" %>

<!DOCTYPE html>
<html>
<head>
   <title>대시보드</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <style>
        .dashboard-container {
            max-width: 800px;
            margin: 20px auto;
            padding: 20px;
        }
        .header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
        }
        .user-info {
            margin-bottom: 20px;
        }
        .protected-content {
            border: 1px solid #ddd;
            padding: 20px;
            border-radius: 5px;
        }
    </style>
</head>
<body>
    <div class="dashboard-container">
        <div class="header">
            <h2>대시보드</h2>
            <button onclick="logout()">로그아웃</button>
        </div>
        <div id="userInfo" class="user-info"></div>
        <div class="protected-content">
            <h3>보호된 컨텐츠</h3>
            <div id="protectedData"></div>
        </div>
    </div>

    <script>
        // 페이지 로드 시 인증 확인
        $(document).ready(function() {
            checkAuth();
            loadUserInfo();
            loadProtectedData();
        });

        // 인증 확인
        function checkAuth() {
            const token = localStorage.getItem('jwt_token');
            if (!token) {
                window.location.href = 'Login.aspx';
                return;
            }
        }

        // 사용자 정보 로드
        function loadUserInfo() {
            const userInfo = JSON.parse(localStorage.getItem('user_info'));
            if (userInfo) {
                $('#userInfo').html(`
                    <p>사용자명: ${userInfo.username}</p>
                    <p>역할: ${userInfo.role}</p>
                `);
            }
        }

        // 보호된 데이터 로드
        function loadProtectedData() {
            $.ajax({
                url: 'Dashboard.aspx/GetProtectedData',
                type: 'POST',
                contentType: 'application/json',
                headers: {
                    'Authorization': 'Bearer ' + localStorage.getItem('jwt_token')
                },
                success: function(response) {
                    $('#protectedData').html(response.d.data);
                },
                error: function(xhr) {
                    if (xhr.status === 401) {
                        // 인증 실패 시 로그인 페이지로 리다이렉트
                        logout();
                    } else {
                        $('#protectedData').html('데이터 로드 중 오류가 발생했습니다.');
                    }
                }
            });
        }

        // 로그아웃
        function logout() {
            localStorage.removeItem('jwt_token');
            localStorage.removeItem('user_info');
            window.location.href = 'Login.aspx';
        }

        // API 요청을 위한 공통 함수
        function apiRequest(url, method, data) {
            return $.ajax({
                url: url,
                type: method,
                contentType: 'application/json',
                headers: {
                    'Authorization': 'Bearer ' + localStorage.getItem('jwt_token')
                },
                data: JSON.stringify(data)
            });
        }
    </script>
</body>
</html>
 

이 클라이언트 구현의 주요 특징과 기능을 설명드리겠습니다:

  1. 로그인 페이지 (Login.aspx):
    • 사용자명과 비밀번호 입력 폼 제공
    • AJAX를 사용한 비동기 로그인 처리
    • 로그인 성공 시 JWT 토큰을 localStorage에 저장
    • 에러 메시지 표시 기능
  2. 대시보드 페이지 (Dashboard.aspx):
    • 인증된 사용자만 접근 가능
    • 사용자 정보 표시
    • 보호된 데이터 로드 기능
    • 로그아웃 기능
  3. 주요 보안 기능:
    • 모든 API 요청에 JWT 토큰 포함
    • 인증되지 않은 접근 시 자동 리다이렉트
    • 토큰 만료 시 자동 로그아웃

구현 시 주의사항:

  1. localStorage 대신 보안이 더 필요한 경우 httpOnly 쿠키 사용을 고려해 볼 수 있습니다.
  2. 실제 운영 환경에서는 HTTPS 사용 필수입니다.
  3. CSRF 공격 방지를 위한 대책을 추가해 주는 게 좋습니다.

이상 JWT에 대하여 간단하게 알아보았습니다.

반응형
반응형

이 버전은 Google Play 64비트 요구사항을 준수하지 않습니다.
다음 APK 또는 App Bundle은 64비트 기기를 지원하지만, 32비트 네이티브 코드만 포함합니다. [1]
앱에 64비트 및 32비트 네이티브 코드를 포함하세요. Android App Bundle 게시 형식을 사용하여 각 기기 아키텍처가 자동으로 필요한 네이티브 코드만 수신하도록 하세요. 그래야 앱의 전체 크기를 줄일 수 있습니다.

 

이런 에러가 발생하였습니다.

 

해결방법은 Project Settings > Player > Other Settings > Target Architectures > ARM64를 체크해주시면 됩니다.

 

반응형
반응형

1. Unity에서 화면을 고정하는 방법 (화면 방향 고정)

Unity에서는 화면 방향을 특정 방향으로 고정할 수 있습니다.

방법 1: Player Settings에서 설정 (정적인 방식)

  1. EditProject SettingsPlayer로 이동
  2. Resolution and Presentation 탭에서 Default Orientation 설정 변경
    • Portrait (세로 모드)
    • Portrait Upside Down (뒤집힌 세로 모드)
    • Landscape Left (가로 모드, 카메라가 왼쪽인 상태)
    • Landscape Right (가로 모드, 카메라가 오른쪽인 상태)
    • Auto Rotation (자동 회전)
  3. Auto Rotation 선택 시, 원하는 방향을 활성화/비활성화 가능

설정위치

 

#저는 이번 프로젝트가 가로모드로만 작동하는 방식이라서 카메라가 왼쪽일 경우 오른쪽일 경우의 가로모드만 체크하였습니다.

 

방법 2: 코드로 화면 방향 고정 (동적인 방식)

게임 내에서 특정 씬에서만 화면 방향을 바꾸려면 C# 코드로 설정할 수 있습니다.

using UnityEngine;

public class ScreenOrientationController : MonoBehaviour
{
    void Start()
    {
        // 세로 화면 고정
        Screen.orientation = ScreenOrientation.Portrait;

        // 또는 가로 화면 고정
        // Screen.orientation = ScreenOrientation.LandscapeLeft;
    }
}
 

위 스크립트를 특정 씬에서 사용하면 해당 씬에서는 설정된 방향으로 고정됩니다.


2. 씬마다 가로/세로 변경하는 방법 (동적 변경)

씬이 변경될 때마다 화면 방향을 변경하고 싶다면 SceneManager.sceneLoaded 이벤트를 활용할 수 있습니다.

씬 변경 시 자동으로 화면 방향 바꾸기

using UnityEngine;
using UnityEngine.SceneManagement;

public class OrientationManager : MonoBehaviour
{
    void Awake()
    {
        // 씬이 로드될 때마다 호출
        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        // 씬 이름 또는 인덱스에 따라 화면 방향 변경
        if (scene.name == "MainMenu")  
        {
            Screen.orientation = ScreenOrientation.Portrait;  // 세로 모드
        }
        else if (scene.name == "GameScene")
        {
            Screen.orientation = ScreenOrientation.LandscapeLeft;  // 가로 모드
        }
    }

    void OnDestroy()
    {
        // 이벤트 해제 (메모리 누수 방지)
        SceneManager.sceneLoaded -= OnSceneLoaded;
    }
}

설명

  • SceneManager.sceneLoaded를 사용해 씬이 로드될 때마다 자동으로 화면 방향을 변경
  • 특정 씬에서는 세로(Portrait), 다른 씬에서는 가로(LandscapeLeft)로 설정 가능

이렇게 하면 각 씬에 맞춰 자동으로 화면 방향이 변경됩니다.

 

반응형
반응형

1. PlayerPrefs란?

PlayerPrefs는 Unity에서 간단한 데이터 저장을 위해 제공하는 기능으로, 게임의 설정 값이나 간단한 데이터를 영구적으로 저장하는 데 사용됩니다. 내부적으로 Windows에서는 레지스트리, Android/iOS에서는 로컬 파일을 이용하여 데이터를 저장합니다.


2. PlayerPrefs 사용법

기본적인 데이터 저장 및 불러오기

// 데이터 저장
PlayerPrefs.SetInt("HighScore", 1000);
PlayerPrefs.SetFloat("Volume", 0.8f);
PlayerPrefs.SetString("PlayerName", "Alice");

// 데이터 불러오기 (기본값 설정 가능)
int highScore = PlayerPrefs.GetInt("HighScore", 0);
float volume = PlayerPrefs.GetFloat("Volume", 1.0f);
string playerName = PlayerPrefs.GetString("PlayerName", "Guest");

// 데이터 삭제
PlayerPrefs.DeleteKey("HighScore");

// 모든 데이터 삭제
PlayerPrefs.DeleteAll();

// 변경 사항 저장 (모바일에서 필요할 수도 있음)
PlayerPrefs.Save();

3. PlayerPrefs의 장점과 단점

장점

  1. 간단한 데이터 저장 가능
    • 별도의 파일 I/O 작업 없이 간단한 코드로 데이터를 저장할 수 있습니다.
  2. 플랫폼 독립적
    • Windows, Mac, Android, iOS 등에서 동일한 방식으로 사용할 수 있습니다.
  3. 설정 값 저장에 적합
    • 볼륨, 해상도, 컨트롤 설정 등의 데이터를 쉽게 저장하고 불러올 수 있습니다.

단점

  1. 보안이 취약함
    • 저장된 데이터가 암호화되지 않으며, Windows에서는 레지스트리를, 모바일에서는 로컬 파일을 조작하면 쉽게 변경할 수 있습니다.
  2. 대량의 데이터 저장에는 부적합
    • PlayerPrefs는 작은 설정 값을 저장하는 용도로 만들어졌으며, 큰 데이터를 저장하면 성능 저하가 발생할 수 있습니다.
  3. Binary나 구조체 저장 불가
    • int, float, string 타입만 저장 가능하며, 리스트나 복잡한 데이터 구조를 저장하려면 JSON 변환이 필요합니다.
    • 예제:
       
string json = JsonUtility.ToJson(myData);
PlayerPrefs.SetString("MyData", json);

4. PlayerPrefs를 사용해야 할 때 vs 다른 대안

기능PlayerPrefs대안

설정 값 저장 ✅ 적합 -
게임 진행 데이터 저장 ❌ 부적합 JSON 파일, SQLite, Binary Serialization, Cloud Save
보안이 중요한 데이터 (예: 유료 아이템) ❌ 매우 부적합 암호화된 파일, 클라우드 저장 (Firebase, PlayFab 등)

5. 결론

  • PlayerPrefs는 설정 값을 저장하는 데 유용하지만, 중요한 게임 데이터(점수, 진행 상황, 인벤토리 등)를 저장하는 용도로 사용하면 보안 문제가 발생할 수 있습니다.
  • 큰 데이터 저장이 필요하다면 파일 시스템, JSON, SQLite, 클라우드 저장을 고려하는 것이 좋습니다.

🔹 추천 사용 예시

✅ PlayerPrefs 사용: 볼륨, 해상도, 키 설정 저장
❌ PlayerPrefs 사용 X: 세이브 데이터, 보안이 중요한 정보

반응형
반응형

HtmlAgilityPack은 HTML 문서를 쉽게 분석하고 조작할 수 있는 .NET용 라이브러리입니다. ASP.NET (ASPX) 환경에서도 사용할 수 있으며, 주로 웹 스크래핑, HTML 파싱, 특정 요소 추출 등에 활용됩니다. 

 

1. HtmlAgilityPack 설치

HtmlAgilityPack은 NuGet 패키지로 제공됩니다. 다음 단계를 통해 프로젝트에 추가할 수 있습니다.

  1. Visual Studio에서 NuGet 패키지 관리자 콘솔 열기
    • 도구 > NuGet 패키지 관리자 > 패키지 관리자 콘솔 클릭.
  2. NuGet 패키지 설치
Install-Package HtmlAgilityPack

 

  1. 설치가 완료되면 프로젝트에 HtmlAgilityPack 라이브러리가 추가됩니다.

2. HTML 파싱 기본 코드

using System;
using System.Net.Http;
using HtmlAgilityPack;

public partial class WebForm1 : System.Web.UI.Page
{
    protected async void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            string url = "https://example.com"; // 분석할 URL
            string htmlContent = await GetHtmlFromUrlAsync(url);

            // HtmlDocument로 로드
            HtmlDocument htmlDoc = new HtmlDocument();
            htmlDoc.LoadHtml(htmlContent);

            // 예: <title> 태그 추출
            var titleNode = htmlDoc.DocumentNode.SelectSingleNode("//title");
            string title = titleNode != null ? titleNode.InnerText : "Title not found";

            // 예: 특정 클래스의 div 요소 추출
            var divNodes = htmlDoc.DocumentNode.SelectNodes("//div[@class='example-class']");
            if (divNodes != null)
            {
                foreach (var div in divNodes)
                {
                    Response.Write($"<p>{div.InnerText}</p>");
                }
            }

            // 결과 출력
            Response.Write($"<h1>Page Title: {title}</h1>");
        }
    }

    private async Task<string> GetHtmlFromUrlAsync(string url)
    {
        using (HttpClient client = new HttpClient())
        {
            // URL에서 HTML 가져오기
            HttpResponseMessage response = await client.GetAsync(url);
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }
    }
}
 

3. 코드 설명

  1. HttpClient 사용:
    • 지정한 URL에서 HTML 콘텐츠를 가져옵니다.
    • HttpClient를 통해 비동기 방식으로 HTML을 다운로드합니다.
  2. HtmlAgilityPack.HtmlDocument:
    • HTML을 파싱하고 DOM을 생성합니다.
    • LoadHtml() 메서드를 사용하여 HTML 문자열을 로드합니다.
  3. XPath를 사용한 노드 검색:
    • SelectSingleNode("//title"): HTML 문서에서 <title> 태그를 가져옵니다.
    • SelectNodes("//div[@class='example-class']"): 특정 클래스를 가진 <div> 태그들을 가져옵니다.
  4. 결과 출력:
    • Response.Write로 분석된 내용을 클라이언트에 출력합니다.

4. HTML 분석 시 주의사항

  • URL의 HTML 구조가 자주 바뀌는 경우 XPath를 업데이트해야 할 수 있습니다.
  • 웹사이트에 따라 robots.txt 파일을 확인하여 크롤링이 허용되는지 확인하세요.
  • HTML 인코딩 문제를 처리하려면 HtmlAgilityPack의 OptionReadEncoding 속성을 사용할 수 있습니다:
     
htmlDoc.OptionReadEncoding = true;

 

 

더 자세하게 HtmlAgilityPack으로 노드를 찾는 법을 알아봅시다.

 

HtmlAgilityPack에서 노드를 찾는 방법은 XPath를 사용하여 HTML 문서의 특정 요소를 선택하는 방식으로 이루어집니다. XPath는 XML 및 HTML 문서에서 요소를 검색하기 위한 쿼리 언어로, 다양한 패턴을 지원합니다. 아래에 구체적인 예제를 통해 SelectSingleNode 및 SelectNodes를 사용하는 방법과 XPath 문법을 자세히 설명하겠습니다.

 

1. 기본 노드 찾기

HTML 문서에서 특정 태그를 직접 선택할 수 있습니다.

예제 HTML

<!DOCTYPE html>
<html>
<head>
    <title>Sample Page</title>
</head>
<body>
    <h1>Welcome to the Sample Page</h1>
    <p>This is a sample paragraph.</p>
    <a href="https://example.com">Example Link</a>
</body>
</html>

XPath 예제

코드

HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(htmlContent);

// 1. <title> 태그 가져오기
var titleNode = htmlDoc.DocumentNode.SelectSingleNode("//title");
Console.WriteLine(titleNode.InnerText); // Output: Sample Page

// 2. <h1> 태그 가져오기
var h1Node = htmlDoc.DocumentNode.SelectSingleNode("//h1");
Console.WriteLine(h1Node.InnerText); // Output: Welcome to the Sample Page

// 3. <p> 태그 가져오기
var pNode = htmlDoc.DocumentNode.SelectSingleNode("//p");
Console.WriteLine(pNode.InnerText); // Output: This is a sample paragraph.
 

2. 속성을 기준으로 노드 찾기

특정 속성을 가진 태그를 선택할 수 있습니다.

예제 HTML

<div class="header">Header Section</div>
<div class="content">Main Content</div>
<div id="footer" class="footer">Footer Section</div>
 

XPath 예제

// 1. class가 "header"인 <div> 가져오기
var headerNode = htmlDoc.DocumentNode.SelectSingleNode("//div[@class='header']");
Console.WriteLine(headerNode.InnerText); // Output: Header Section

// 2. id가 "footer"인 <div> 가져오기
var footerNode = htmlDoc.DocumentNode.SelectSingleNode("//div[@id='footer']");
Console.WriteLine(footerNode.InnerText); // Output: Footer Section

// 3. class가 "content"인 모든 <div> 찾기
var contentNodes = htmlDoc.DocumentNode.SelectNodes("//div[@class='content']");
foreach (var node in contentNodes)
{
    Console.WriteLine(node.InnerText); // Output: Main Content
}

3. 여러 노드 선택

여러 노드를 선택하려면 SelectNodes를 사용합니다.

예제 HTML

<ul>
    <li>Apple</li>
    <li>Banana</li>
    <li>Cherry</li>
</ul>

XPath 예제

// <li> 태그들 모두 가져오기
var liNodes = htmlDoc.DocumentNode.SelectNodes("//li");

foreach (var node in liNodes)
{
    Console.WriteLine(node.InnerText);
}
// Output:
// Apple
// Banana
// Cherry

4. 구체적인 XPath 패턴

XPath를 활용하면 더욱 세부적으로 노드를 선택할 수 있습니다.

예제 HTML

<div class="product">
    <h2>Product 1</h2>
    <span class="price">$10</span>
</div>
<div class="product">
    <h2>Product 2</h2>
    <span class="price">$20</span>
</div>

XPath 예제

// 첫 번째 제품의 이름과 가격 가져오기
var firstProductName = htmlDoc.DocumentNode.SelectSingleNode("//div[@class='product']/h2").InnerText;
var firstProductPrice = htmlDoc.DocumentNode.SelectSingleNode("//div[@class='product']/span[@class='price']").InnerText;

Console.WriteLine($"{firstProductName} - {firstProductPrice}");
// Output: Product 1 - $10

// 모든 제품 정보 가져오기
var products = htmlDoc.DocumentNode.SelectNodes("//div[@class='product']");
foreach (var product in products)
{
    var productName = product.SelectSingleNode("./h2").InnerText; // 현재 노드 기준 검색
    var productPrice = product.SelectSingleNode("./span[@class='price']").InnerText;
    Console.WriteLine($"{productName} - {productPrice}");
}
// Output:
// Product 1 - $10
// Product 2 - $20

5. XPath 문법 정리

  • //tag
    특정 태그를 문서 전체에서 찾음.
    예: //div - 모든 <div> 태그.
  • ./tag
    현재 노드의 하위 태그를 찾음.
    예: ./span - 현재 노드 아래의 <span> 태그.
  • //tag[@attr='value']
    특정 속성을 가진 태그를 찾음.
    예: //div[@class='content'] - class가 content인 <div> 태그.
  • //tag[contains(@attr, 'value')]
    속성 값이 특정 문자열을 포함하는 태그를 찾음.
    예: //div[contains(@class, 'prod')] - class에 prod가 포함된 <div>.
  • //tag[text()='value']
    특정 텍스트 값을 가진 태그를 찾음.
    예: //span[text()='$10'] - 텍스트가 $10인 <span> 태그.

6. 전체 예제

아래는 다양한 XPath를 활용하여 HTML 문서를 분석하는 완전한 예제입니다.

using HtmlAgilityPack;
using System;

class Program
{
    static void Main()
    {
        string htmlContent = @"
        <html>
            <body>
                <div class='product'>
                    <h2>Product A</h2>
                    <span class='price'>$30</span>
                </div>
                <div class='product'>
                    <h2>Product B</h2>
                    <span class='price'>$50</span>
                </div>
            </body>
        </html>";

        HtmlDocument htmlDoc = new HtmlDocument();
        htmlDoc.LoadHtml(htmlContent);

        // 모든 제품 정보 가져오기
        var products = htmlDoc.DocumentNode.SelectNodes("//div[@class='product']");
        foreach (var product in products)
        {
            var productName = product.SelectSingleNode("./h2").InnerText;
            var productPrice = product.SelectSingleNode("./span[@class='price']").InnerText;
            Console.WriteLine($"{productName}: {productPrice}");
        }
    }
}

출력 결과:

Product A: $30
Product B: $50
반응형
반응형

유니티에서 1.0f != 1.0f가 true로 나오는 이유는 부동소수점(Floating Point) 비교의 정확도 문제 때문일 가능성이 매우 높습니다. 부동소수점은 컴퓨터에서 실수를 표현할 때 근사치를 사용하기 때문에, 동일해 보이는 값도 내부적으로는 미세한 차이가 있을 수 있습니다.

이유 설명:

  1. 부동소수점 표현의 제한
    • 1.0라는 숫자는 컴퓨터 내부에서 이진수로 변환됩니다.
    • 그러나 10진수인 는 2진수로 완벽히 표현되지 않아 근사치로 저장됩니다.
    • 이 때문에, 두 개의 5.2를 비교할 때 내부적으로 미세한 차이가 발생할 수 있습니다.
  2. 연산 과정에서의 오차
    • 부동소수점 값을 연산하거나 다른 변수에 저장하면, 그 과정에서 추가적인 오차가 누적될 수 있습니다.
    • 이 오차가 비교 연산(!=)에서 영향을 미칩니다.
  3. 부동소수점 비교의 일반적인 결과
    • == 연산이나 != 연산은 값을 직접 비교하므로, 미세한 오차가 있으면 false나 true로 결과가 달라질 수 있습니다.

해결 방법:

부동소수점 비교에서는 항상 **허용 오차(Epsilon)**를 사용하여 값을 비교하는 것이 좋습니다.

코드 예제:

float a = 5.2f;
float b = 5.2f;

// 허용 오차 정의
float epsilon = 0.00001f;

// 직접 비교 대신 허용 오차를 사용
if (Mathf.Abs(a - b) < epsilon)
{
    Debug.Log("a와 b는 같다.");
}
else
{
    Debug.Log("a와 b는 다르다.");
}

설명:

  • Mathf.Abs(a - b)는 두 값의 차이의 절대값을 계산합니다.
  • 이 차이가 epsilon보다 작으면 두 값이 같다고 간주합니다.

추가 참고:

  • 부동소수점 문제는 단순히 Unity의 문제가 아니라, 대부분의 프로그래밍 언어에서 공통적으로 발생합니다.
  • Unity에서는 부동소수점 비교에 Mathf.Approximately 메서드를 제공하므로 이를 활용하는 것도 좋은 방법입니다:
if (Mathf.Approximately(a, b))
{
    Debug.Log("a와 b는 같다.");
}
else
{
    Debug.Log("a와 b는 다르다.");
}
 

Mathf.Approximately는 부동소수점 비교에 적합한 허용 오차를 내부적으로 적용해줍니다.

 

float값을 더하거나 빼거나 한 경우에는 값이 미세하게 달라지는 경우가 많습니다.

이러한 작업은 나중에 디버깅하기 귀찮고 예상하지 못했던 에러가 발생합니다.

 

모두 조심해서 즐거운 코딩이 되셨으면 좋겠습니다.

반응형

+ Recent posts