From 178776d307faf04f99739d7c148a1da5eef44789 Mon Sep 17 00:00:00 2001 From: user01 Date: Tue, 7 Jul 2026 17:55:26 +0900 Subject: [PATCH] =?UTF-8?q?&=20=ED=8A=B9=EC=88=98=EB=AC=B8=EC=9E=90?= =?UTF-8?q?=EC=97=90=20amp;=20=EC=B2=98=EB=A6=AC=EA=B0=80=20=EB=90=98?= =?UTF-8?q?=EC=96=B4=20=EC=9E=88=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20--fix-xml=20=EC=98=B5=EC=85=98=EC=9D=84=20=ED=86=B5?= =?UTF-8?q?=ED=95=B4=20=EC=88=98=EC=A0=95=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/main.py b/main.py index 819fd3a..ca5cde2 100755 --- a/main.py +++ b/main.py @@ -40,6 +40,7 @@ def main(argc, argv): parser.add_argument('--metadata', dest='bMetadata', action='store_true', help='.metadata 파일로부터 ComicInfo.xml 생성 및 CBZ에 추가') parser.add_argument('--verbose', dest='bVerbose', action='store_true', help='상세 로그 출력') parser.add_argument('--fix-arc-path', dest='bFixArcPath', action='store_true', help='CBZ 아카이브의 내부 폴더 구조를 제거합니다.') + parser.add_argument('--fix-xml', dest='bFixXml', action='store_true', help='ComicInfo.xml의 특수문자(&) 이스케이프 및 문법 오류를 검사하고 수정합니다.') parser.add_argument('-v', '--version', action='version', version='ComicInfoXMLConv 1.0') args = parser.parse_args(argv[1:]) # argv[0]은 스크립트 이름 제외 @@ -114,6 +115,25 @@ def process_archive_file(archive_path: str, args: argparse.Namespace): util.DbgOut(f"comicinfo.xml 발견됨: {archive_path} (경로: {comicinfo_path_in_zip})", True) comicinfo_xml_bytes = util.GetZippedFileByte(archive_path, comicinfo_path_in_zip) if comicinfo_xml_bytes: + fixed_xml = None + if args.bFixXml: + fixed_xml, msgs = ValidateAndFixComicInfoXML(comicinfo_xml_bytes) + for msg in msgs: + util.DbgOut(msg, True) + if fixed_xml is not None: + comicinfo_xml_bytes = fixed_xml + + has_mods = any([arc_title, arc_writer, arc_series, arc_tags, + arc_number, arc_volume, args.info_count]) + + if not has_mods: + if fixed_xml is not None: + util.AddFileToZip(archive_path, comicinfo_path_in_zip, fixed_xml) + util.DbgOut(f"ComicInfo.xml 특수문자 수정 완료: {archive_path}", True) + if args.bPrint: + print(comicinfo_xml_bytes.decode('utf-8')) + return + modified = ModComicInfoXML(comicinfo_xml_bytes, arc_title, arc_writer, arc_series, arc_tags, info_number=arc_number, info_volume=arc_volume, info_count=args.info_count) @@ -123,6 +143,9 @@ def process_archive_file(archive_path: str, args: argparse.Namespace): elif modified: util.AddFileToZip(archive_path, comicinfo_path_in_zip, modified) util.DbgOut(f"ComicInfo.xml 업데이트됨: {archive_path} (경로: {comicinfo_path_in_zip})", True) + elif fixed_xml is not None: + util.AddFileToZip(archive_path, comicinfo_path_in_zip, fixed_xml) + util.DbgOut(f"ComicInfo.xml 특수문자 수정 완료: {archive_path}", True) else: util.DbgOut(f"comicinfo.xml 없음: {archive_path}", True) if arc_volume or arc_number or arc_title or arc_writer or arc_series or arc_tags or args.info_count: @@ -255,6 +278,38 @@ def ResolveRegexTransform(text: str | None, current_value: str | None) -> str | return text +def ValidateAndFixComicInfoXML(xml_bytes: bytes) -> tuple[bytes | None, list[str]]: + try: + ET.fromstring(xml_bytes) + return (None, ["XML is well-formed."]) + except ET.ParseError as e: + msgs = [f"Parse error: {e}"] + + try: + text = xml_bytes.decode('utf-8') + except UnicodeDecodeError: + msgs.append("Cannot decode XML (expected UTF-8).") + return (None, msgs) + + text, count = re.subn( + r'&(?!(?:amp|lt|gt|quot|apos);|#\d+;|#x[0-9a-fA-F]+;)', + '&', + text + ) + if count > 0: + msgs.append(f"Fixed {count} unescaped & character(s).") + fixed_bytes = text.encode('utf-8') + try: + ET.fromstring(fixed_bytes) + msgs.append("XML is now well-formed.") + except ET.ParseError as e2: + msgs.append(f"Remaining issues after fix: {e2}") + return (fixed_bytes, msgs) + + msgs.append("No fixable issues found.") + return (None, msgs) + + def ParsePathForComicInfo(strFullPath: str) -> dict: """ 파일 경로에서 작가, 제목, 번호를 추출합니다.