AAoM-02: W3C 호환 XML 파서

발행: (2026년 1월 14일 오전 11:32 GMT+9)
9 min read
원문: Dev.to

Source: Dev.to

스킬

나는 아직도 MoonBit 시스템 프롬프트와 IDE 스킬을 사용하여 Claude Code (Opus 4.5)를 사용하고 있습니다.
게다가 MoonBit 언어에 대한 모범 사례와 일반적인 함정을 AI에게 알려주기 위해 moonbit-lang이라는 새로운 스킬을 만들었습니다. 헤더는 다음과 같습니다:

---
name: moonbit-lang
description: "MoonBit language reference and coding conventions. Use when writing MoonBit code, asking about syntax, or encountering MoonBit-specific errors. Covers error handling, FFI, async, and common pitfalls."
---

# MoonBit Language Reference

@reference/fundamentals.md
@reference/error-handling.md
@reference/ffi.md
@reference/async-experimental.md
@reference/package.md
@reference/toml-parser-parser.mbt

이 스킬 문서에서는 AI가 익숙하지 않은 공식 파일‑I/O 패키지 moonbitlang/x/fs도 언급하고 있습니다.
전체 스킬 문서와 참고 자료는 GitHub에서 확인할 수 있으며, 나는 사용 중인 스킬들을 지속적으로 업데이트하고 있습니다.

AI(Codex와 Claude 모두)는 시작 시 설명만 읽고 나머지는 필요할 때 불러옵니다. 나는 스킬 문서를 간단하게 유지하는데, 이는 지나치게 긴 문서가 AI가 세부 사항을 이해하는 데 방해가 된다는 내 경험에 기반합니다.

Problem

XML은 구성 파일, 데이터 교환, 레거시 시스템에서 여전히 널리 사용됩니다. 규격에 맞는 XML 파서는 다음을 처리해야 합니다:

  • 요소 태그, 속성, 네임스페이스
  • 엔티티 참조

아래는 최소 문서를 파싱하고 결과 이벤트 스트림을 검사하는 간단한 테스트입니다:

let xml = "\n\n\n"
let reader = Reader::from_string(xml)
let events : Array[Event] = []
for {
  match reader.read_event() {
    Eof => {
      events.push(Eof)
      break
    }
    event => events.push(event)
  }
}
inspect(
  to_libxml_format(events),
  content="[DocType(\"doc\"), Empty({name: \"doc\", attributes: []}), Eof]",
)

잘못된 형식의 예시:

test "w3c/not-wf/not_wf_sa_001" {
  // Attribute values must start with attribute names, not "?".
  let xml = "\n\n\n"
  let reader = Reader::from_string(xml)
  let has_error = for {
    try reader.read_event() catch {
      _ => break true
    } noraise {
      Eof => break false
      _ => continue
    }
  }
  inspect(has_error, content="true")
}

735개의 테스트가 생성되었으며, 약 14 k줄의 코드로 구성됩니다. 몇 개의 수동 테스트를 추가한 후, 현재 스위트에는 800개의 테스트가 포함되어 있습니다.

파서 구현

quick‑xml이 초기 레퍼런스였기 때문에 Claude는 이를 기반으로 한 풀‑파서 아키텍처를 따랐으며, 이는 우리의 목표에 적합하다고 생각했습니다. API는 다음과 같습니다:

let reader = @xml.Reader::from_string(xml)
for {
  match reader.read_event() {
    Eof => break
    Start(elem) => println("Start: \{elem.name}")
    End(name)   => println("End: \{name}")
    Text(content) => println("Text: \{content}")
    _ => continue
  }
}

lxml이 트리를 반환하는 반면 우리 파서는 이벤트를 발생시키므로, Claude에게 이벤트 스트림을 lxml이 생성하는 정확한 형식으로 변환하는 to_libxml_format 함수를 구현하도록 요청했습니다. 이를 통해 테스트 비교가 간단해졌습니다.

기본 구현은 AI‑전용 작업만으로 약 4시간 정도 걸렸으며(가끔 “계속 진행해 주세요” 프롬프트 제외), 가장 복잡한 기능은 DTD 파싱 및 검증이었습니다. 구현을 구조화하기 위해 Claude의 플랜 모드를 사용했습니다. 아래는 그 플랜의 요약입니다:

Plan summary

프로젝트 요약

Diagram

1 시간 정도 후에 DTD 지원이 구현되었고 726개의 테스트가 통과했습니다.
그 후 3 시간이 더 걸려 다음과 같은 엣지 케이스들을 처리했습니다:

  • 엔티티 값 확장
  • 텍스트‑분할 세부 사항
  • UTF‑8 BOM 처리

결과

노력의 끝에서 800개의 W3C 호환성 테스트가 통과되었습니다.

  • 59개의 테스트tests‑gen 스크립트에 의해 건너뛰어졌습니다 because:
    • 일부는 유효했지만 lxml에 의해 거부되었습니다.
    • 다른 일부는 형식이 올바르지 않았지만 lxml에 의해 통과되었습니다.

이것들은 “lxml 구현 특이점”으로 표시되었습니다.
가장자리 경우가 지나치게 복잡했기 때문에 각각을 자세히 검증하지는 않았지만, 남은 800개의 테스트는 충분한 신뢰를 제공했습니다.

지원되는 기능

  • XML 1.0 + Namespaces 1.0
  • 메모리 효율적인 스트리밍을 위한 Pull‑parser API
  • XML 생성을 위한 Writer API
  • 엔터티 확장을 포함한 DTD 지원

회고

잘된 점?

  • 공식 테스트 스위트 사용 – W3C 호환성 테스트를 통해 문자 참조, DTD 특이점, 네임스페이스 처리 등과 같은 드물게 발생하는 엣지 케이스를 발견했으며, 이는 수동으로 테스트할 생각조차 못했을 것이다.
  • 레퍼런스 구현 전환quick‑xml은 의도적으로 관대하게 동작해 호환성 테스트가 어려웠다. libxml2로 전환하면서 엄격한 레퍼런스를 얻을 수 있었다.
  • 복잡한 기능을 위한 계획 모드 – DTD 파싱을 계획으로 나누어 작업을 체계화했으며, 그렇지 않으면 관련 없는 버그 사이를 오가며 작업했을 것이다.

겪은 어려움

Claude가 파서를 고치는 대신 테스트를 수정하려고 자주 시도했다:

  • 잘못된 출력에 맞추기 위해 테스트 기대값을 변경함.
  • 실패하는 테스트를 건너뛰도록 테스트 생성기를 업데이트함.
  • 테스트를 “관대함”으로 표시하고 건너뛰게 함.

나는 Claude에게 계속해서 “테스트가 아니라 MoonBit 구현을 업데이트해라.” 라고 상기시켜야 했다.

다른 반복적인 문제들:

  • 프로젝트 규칙을 잊어버림 (예: 탐색에 moon‑ide 스킬을 사용하지 않거나, try/catch/noraise 대신 match (try? expr) 사용).
  • 이러한 규칙을 CLAUDE.md에 추가했지만 문제를 완전히 해결하지는 못했다.

레딧에서 관련 논의를 찾았다 (link). 여기서는 Opus 4.5Sonnet 4.5에 버그가 있다고 제시하고 있다. 곧 수정되길 바란다.

향후 작업

앞으로 더 많은 파서를 구현하거나 포팅해야 할 것으로 예상한다. 파서를 작성하고 표준 기반 테스트 스크립트를 생성한 경험을 재사용 가능한 스킬이나 명령으로 전환하여 다음 프로젝트가 이 기반 위에서 혜택을 받을 수 있도록 할 계획이다.

시간 투자 (≈ 10 시간)

활동시간
테스트 생성 스크립트의 협업 탐색2
기본 기능의 자율 구현4
DTD, 네임스페이스, 엔터티 계획 및 구현1
에지 케이스 처리 (테스트 실패 17건 수정)3

코드는 GitHub에 있습니다:

Back to Blog

관련 글

더 보기 »

중첩 마크업

번역할 텍스트를 제공해 주시겠어요? 텍스트가 없으면 번역을 진행할 수 없습니다.