2024년 3월 22일 금요일

python xml comment parsing

python xml comment parsing

python에서는 xml 파싱하는 방법은 여러가지 준비가 되어 있습니다.

그런데 간혹 comment 까지 파싱 해야 하는 경우가 있습니다. 

XML은 데이터를 저장하고 전송하는 데 사용되는 마크업 언어입니다. 다음은 XML의 간단한 예제입니다

python에서 아래와 같은 xml을 string에 담았습니다.

xml_str = \
"""<?xml version="1.0" encoding="UTF-8"?>
<!-- hello -->
<note>
<!-- dash1 -->
<to>kim</to><!-- dash2 -->
<from>lee</from>
<heading>alert</heading>
<body>i am a boy</body>
</note>
"""

여기에서 XML 파서는 lxml 을 사용합니다. 이 패키지는 comment까지 완벽하게 처리해냅니다.

기본 xml 처리는 아래와 같은 형태를 사용 가능합니다.

def print_xml(xml_str_):
# XML 파일을 불러옵니다.
root = etree.fromstring(xml_str.encode())

# 루트 엘리먼트의 태그와 속성을 출력합니다.
print(f'Root element: {root.tag}')
for name, value in root.attrib.items():
print(f'Attribute - {name}: {value}')

# 모든 자식 엘리먼트를 순회하며 출력합니다.
for child in root:
print(f'Child element: {child.tag}')
for name, value in child.attrib.items():
print(f'Attribute - {name}: {value}')

결과를 아래와 같이 실행시키면

print_xml(xml_str)

다음과 같은 결과를 얻습니다.

Root element: note
Child element: <cyfunction Comment at 0x00000234BF7132B0>
Child element: to
Child element: <cyfunction Comment at 0x00000234BF7132B0>
Child element: from
Child element: heading
Child element: body

cyfunction 이라고 나오는곳이 comment가 됩니다. 이 부분을 처리하려면 아래와 같은 코드가 필요합니다. tag가 etree.Comment 인지 비교해서 따로 처리하는 방법입니다.

if child.tag == etree.Comment:
print("comment:", child.text)

그러면 처음 예제와 합쳐서 구현해보도록 하겠습니다.

def print_xml_wc(xml_str_):
# XML 파일을 불러옵니다.
root = etree.fromstring(xml_str.encode())

# 루트 엘리먼트의 태그와 속성을 출력합니다.
print(f'Root element: {root.tag}')
for name, value in root.attrib.items():
print(f'Attribute - {name}: {value}')

# 모든 자식 엘리먼트를 순회하며 출력합니다.
for child in root:
print(f'Child element: {child.tag}')
if child.tag == etree.Comment:
print("comment:", child.text)
continue
for name, value in child.attrib.items():
print(f'Attribute - {name}: {value}')

이런 식으로 됩니다.

print_xml_wc(xml_str)

수행한 결과는 아래와 같습니다.

Root element: note
Child element: <cyfunction Comment at 0x00000234BF7132B0>
comment: dash1
Child element: to
Child element: <cyfunction Comment at 0x00000234BF7132B0>
comment: dash2
Child element: from
Child element: heading
Child element: body


xml 데이터의 root node보다 앞쪽에 있는 comment 처리 방법

xml을 자세히 보면 앞쪽 hello 라는 주석을 파싱하지 못하는 부분이 있습니다.

이건 어떻게 해야할까요?

처음에 문서도 보고 한참 헤맸는데요. 의외로 간단합니다.

element의 getprevious() 함수를 사용합니다.

변경된 코드는 아래와 같습니다.

def print_xml_wc_root(xml_str_):
# XML 파일을 불러옵니다.
root = etree.fromstring(xml_str.encode())

if root.getprevious() != None:
if root.getprevious().tag == etree.Comment:
print("comment:", root.getprevious().text)

# 루트 엘리먼트의 태그와 속성을 출력합니다.
print(f'Root element: {root.tag}')
for name, value in root.attrib.items():
print(f'Attribute - {name}: {value}')

# 모든 자식 엘리먼트를 순회하며 출력합니다.
for child in root:
print(f'Child element: {child.tag}')
if child.tag == etree.Comment:
print("comment:", child.text)
continue
for name, value in child.attrib.items():
print(f'Attribute - {name}: {value}')

root node의 앞쪽 tag 가 comment 라면 출력하는 코드를 넣었습니다.

실행한 결과는 아래와 같고

comment:  hello 
Root element: note
Child element: <cyfunction Comment at 0x0000026F045732B0>
comment: dash1
Child element: to
Child element: <cyfunction Comment at 0x0000026F045732B0>
comment: dash2
Child element: from
Child element: heading
Child element: body

전체 소스는 git hub에서 보시기 바랍니다.

sourcecode/python/example/_63_lxml/lxml_comment.py at main · donarts/sourcecode · GitHub



2024년 3월 21일 목요일

Unity Text scroll credits 만들기

 Unity로 Text가 위로 스크롤 되는 credits 입니다.

완성된 영상 입니다.


동작 원리

텍스트 스크롤 영역을 정하고 그곳에 텍스트를 주기적으로 갱신합니다.
스크롤은 Update에서 적절히 진행하며 특정 크기(텍스트 높이)만큼 스크롤이 되면 한줄을 추가하고 스크롤된 만큼 다시 스크롤 영역을 빼서 내려줍니다. 
모든 텍스트가 스크롤 되어 올라갈때까지 이 과정을 반복합니다.

소스 코드

https://drive.google.com/file/d/1XnTsYZyoe1uWSm1CW4SVWuKI_TBQg3KV/view?usp=sharing


조정 인자


MaxLinesOnScreen

화면에 Text가 몇 줄이 나타나는지 나타내는 숫자

Text 폰트나 화면 크기에 따라 화면에 표시되는 글자의 수가 다르므로 적절히 변경해야합니다.

줄 수를 알 수 있도록 Credits_Text 에 숫자를 넣어 놓았습니다. 여기에서는 25줄입니다만 더 작을 수도 늘어날 수도 있습니다.

CreditsFile

text 형태로 된 파일

LineHeight

텍스트의 높이

ScrollSpeed

스크롤 속도



2024년 3월 16일 토요일

OnScreen Controls not working correctly in Unity

 

Unity On Screen 사용시 컨트롤이 되지 않는 문제

증상은 

Player Input에 Auto-Switch off 상태에서 Debug 모드상 Keyboard&Mouse 가 고정되어 On screen game pad를 사용할 수 없는 증상


댓글에 유사 증상 언급이 있음

On Screen Controls not working correctly. - Unity Forum


해결 방법

Runtime에서 Player Input 컴포넌트를 disable/enable 했을때 정상적으로 동작되는지 확인 필요


정상 동작된다면 Player Input 컴포넌트를 off된 상태에서 시작해서 Start에서 enable 해줍니다.

다음과 같은 형태로 start 시 enable 시켰음
void Start()
{
PlayerInput playerInput = GetComponent<PlayerInput>();
playerInput.enabled = true;
}





2024년 3월 10일 일요일

Vector2 Serialize 할때 오류 발생하는 경우

Unity에서 Vector2 Serialize 를 사용하게 되면 아래와 같은 오류가 발생합니다.

SerializationException: Type 'UnityEngine.Vector2' in Assembly 'UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.

아래와같은 Vector2S 와 같은 Class로 변경해주면 사용이 가능합니다.

Vector2 로 된 변수를 Vector2S 에 넣는 것이 가능하기 때문에 Serialize 하는 변수를 Vector2S 변경하고 Vector2 값을 Vector2S 로 복사 하게끔 구현해주면 됩니다.


[Serializable]
public struct Vector2S
{
public float x;
public float y;
public Vector2S(float x, float y)
{
this.x = x;
this.y = y;
}
public override bool Equals(object obj)
{
if (!(obj is Vector2S))
{
return false;
}
var s = (Vector2S)obj;
return x == s.x &&
y == s.y;
}
public Vector2 ToVector2()
{
return new Vector2(x, y);
}
public static bool operator ==(Vector2S a, Vector2S b)
{
return (a.x == b.x) && (a.y == b.y);
}
public static bool operator !=(Vector2S a, Vector2S b)
{
return (a.x != b.x) || (a.y != b.y);
}
public static implicit operator Vector2(Vector2S x)
{
return new Vector2(x.x, x.y);
}
public static implicit operator Vector2S(Vector2 x)
{
return new Vector2S(x.x, x.y);
}
}


2024년 2월 20일 화요일

python requests 사용시 multipart 로 사용하는 방법

python requests 를 사용해서 post를 사용하다면, 기본적으로 application/x-www-form-urlencoded 을 기본적으로 사용하게 됩니다.

그런데 간혹 multipart/form-data 를 원하는 경우가 있습니다.

기본 예제를 살펴 보겠습니다.

import requests

url = 'http://127.0.0.1:5000/funs'
data = {"key1": "value1", "key2": "value2"}

r = requests.post(url, data=data)
print(r.request.headers)
print(r.request.body)

위와 같이 사용하게 되면 'Content-Type': 'application/x-www-form-urlencoded' 이 됩니다.

{'User-Agent': 'python-requests/2.28.1', 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '23', 'Content-Type': 'application/x-www-form-urlencoded'}
key1=value1&key2=value2

그렇다면 multipart 가 되는 조건은 뭘까요?

파일을 첨부 할때입니다.

파일 첨부 예를 만들어 보겠습니다.

import requests

url = 'http://127.0.0.1:5000/funs'
data = {"key1": "value1", "key2": "value2"}

files1 = {'fl': open('test.txt', 'rb')}
r = requests.post(url, files=files1)
print(r.request.headers)
print(r.request.body)


{'User-Agent': 'python-requests/2.28.1', 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '149', 'Content-Type': 'multipart/form-data; boundary=40aac4e5ef113fc32999aeb43e78702e'}
b'--40aac4e5ef113fc32999aeb43e78702e\r\nContent-Disposition: form-data; name="fl"; filename="test.txt"\r\n\r\ntest123\r\n--40aac4e5ef113fc32999aeb43e78702e--\r\n'

결과가 알아보기 어렵게 되긴했지만 'Content-Type': 'multipart/form-data; 가 된것을 알 수 있습니다. 뒤에 있는 boundary는 여러개를 구분하는 용도로 사용하게 됩니다.

처음으로 돌아와서 그렇다면 어떻게 하면 data를 multipart로 전달이 가능할까요?

다른 패키지를 사용하는 방법도 있지만, requests에서는 files와 같이 보내는 방법이 있습니다.

그런데 파일을 보내지 않고 싶은데 어떻게 하냐고요? dummy로 임의의 변수를 넣어 주면 됩니다.

최종 해결책입니다.

import requests

url = 'http://127.0.0.1:5000/funs'
data = {"key1": "value1", "key2": "value2"}


files_dummy = {'fl': None}
r = requests.post(url, data=data, files=files_dummy)
print(r.request.headers)
print(r.request.body)

리턴값입니다.

{'User-Agent': 'python-requests/2.28.1', 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '220', 'Content-Type': 'multipart/form-data; boundary=21825c01c73525d71646d70babe7f0b5'}
b'--21825c01c73525d71646d70babe7f0b5\r\nContent-Disposition: form-data; name="key1"\r\n\r\nvalue1\r\n--21825c01c73525d71646d70babe7f0b5\r\nContent-Disposition: form-data; name="key2"\r\n\r\nvalue2\r\n--21825c01c73525d71646d70babe7f0b5--\r\n'

위에서 'fl' 이란 부분을 보냈는데 requests body부분을 살펴보면 관련 내용이 없음을 알 수 있습니다.


여기에서 사용된 server 소스 입니다.

from flask import Flask, json
from flask import request
api = Flask(__name__)

@api.route('/funs', methods=['POST'])
def post_funs():
    print(request.form)
    print(request.headers)
    return json.dumps({"success": True}), 201

if __name__ == '__main__':
    api.run(debug=True)

 


2024년 2월 18일 일요일

unity c# callback 함수

callback 함수

 콜백 함수(callback function)란 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말합니다. 콜백 함수는 특정 이벤트가 발생하거나 특정 조건이 발생할때 실행될 수 있습니다. 


사용처

버튼을 클릭하면 콜백 함수가 호출되어 원하는 기능을 수행할 수 있습니다. 콜백 함수는 프로그래밍에서 비동기적인 작업을 처리하거나 코드의 재사용성을 높이는 데 유용합니다.


yes/no 를 선택하는 다이얼 로그 예제

다이얼 로그를 띄워주는 class를 만들었습니다. 그런데 해당 class가 닫힐때 특별한 처리를 원하는 경우가 있습니다. 즉 사용자가 yes, no를 선택했는가에 따라 다른 처리를 하고 싶은 경우 입니다. yes no dialog를 여러가지 용도로 사용을 하고자 한다면 그때마다 yes no를 선택할때 다른 코드를 넣어 줘야 합니다.

그런데 그때 호출되어야 하는 함수를 callback 함수 형태로 제작해서 dialog를 생성할때 인자를 넘겨주게 되면 코드 재사용성을 높일 수 있습니다.


unity c# 에서 사용방법

c 언어에서는 보통 함수 포인터라는 것이 있습니다. 함수의 포인터를 함수의 인자로 넘겨서 해당 함수를 호출하는 방식입니다. 그러나 c# 의 경우 포인터라는 개념이 없기 때문에 다른 방법을 이용하게 됩니다. interface라는 방법입니다. 

아래와 같은 형태로 만듭니다. OnYesNo 이름은 나중에 호출을 위한 함수가 됩니다.

public interface DialogYesNoCB
{
    public void OnYesNo(int yesNo);
}

그리고 아래와 같이 다이얼로그가 닫힐때 CloseDialog 함수가 호출되도록 연결해줍니다.  

init 에서는 cb 인자로 interface 함수를 연결합니다. 그리고 실제는 CloseDialog() 함수는 다이얼로가 닫힐때 호출되도록 작업은 따로 해주어야 합니다.

public class DialogYesNo : MonoBehaviour
{
    private DialogYesNoCB cb;
    public void Init(string text, DialogYesNoCB cb)
    {
        GetComponentsInChildren<TMP_Text>(true)[0].enabled = true;
        GetComponentsInChildren<TMP_Text>(true)[0].text = text;
        GetComponentsInChildren<TMP_Text>(true)[1].enabled = false;
        GetComponentsInChildren<Image>(true)[2].enabled = false;
        this.cb = cb;
    }
    public void CloseDialog(int yesNo)
    {
        cb.OnYesNo(yesNo);
    }
}


실제 호출은 아래와 같은 형태로 불리게 됩니다.

아래 코드는 예제 코드로 참고만 하시기 바랍니다. 작업중인 코드에서 가져왔기 때문에 실행 가능한 코드는 아닙니다.

ButtonLevelUp() 함수를 호출하면 YesNo dialog가 뜨고 Yes no 버튼을 누르면 만들어 놓은 OnYesNo 함수가 호출 되도록 되어 있습니다.

public class FieldLevelUp : MonoBehaviour, DialogYesNoCB
{
    public void ButtonLevelUp()
    {
        ShowDialogYesNo("필드를 레벨업 하시겠습니까?", this);
    }
    public void OnYesNo(int yesNo)
    {
        if(yesNo == 1) {
            GameManager.instance.quest.fieldClearCountAfterLUP[(int)fieldName] = 0;
            GameManager.instance.quest.fieldLevel[(int)fieldName]++;
            LeftListUpdate();
        }
    }
    ...
    public void ShowDialogYesNo(string text, DialogYesNoCB cb)
    {

        yesNoDialogPanel.GetComponent<DialogYesNo>().Init(text, cb);
        yesNoDialogPanel.GetComponent<PanelHandler>().Show();
    }
    public void CloseDialogYesNo(int yesNo)
    {
        yesNoDialogPanel.GetComponent<DialogYesNo>().CloseDialog(yesNo);
        yesNoDialogPanel.GetComponent<PanelHandler>().Hide();
    }
}

CloseDialogYesNo는 object yes, no 버튼의 button on click() 함수에 넣도록 합니다.




2024년 2월 11일 일요일

addr2line 구하는 곳 window용

addr2line 이란

addr2line은 디버깅 목적으로 사용되는 편리한 명령 줄 도구입니다. 메모리 주소를 파일 이름과 라인 번호로 변환합니다. 

일반적으로 kernel의 오류를 디버깅 할때가 아니라 so 파일들을 디버깅 할때 사용합니다. 


주의할 점

디버깅 대상이 되는 파일은 디버깅 정보가 포함되도록 컴파일이 되어야 합니다.


실행 예제

-e 옵션을 파일을 지정합니다.

addr2line -e a.out 0x800001(주소)


구하는 방법

android sudio를 설치하면 Android SDK 를 다운로드 가능합니다.

https://developer.android.com/studio

SDK Manager에서 NDK를 선택해서 설치합니다.



경로는 버전에 따라 조금씩 다를 수 있지만 아래 폴더에 존재하게 됩니다.

ndk\26.1.10909125\toolchains\llvm\prebuilt\windows-x86_64\bin