Commit Graph

9 Commits

Author SHA1 Message Date
e62995a7a8 feat(emitting): output encoding for UTF and single-byte families
Add PureXML.Parsing.ByteEncoder, the inverse of ByteDecoder, and a
Serializer.serialize(_:encoding:) / PureXML.serialize(_:encoding:) that
emits bytes in a chosen encoding (the libxml2 save-with-encoding model).
Covers the Unicode transformation formats and the single-byte legacy
families, built by inverting the existing vendored to-Unicode tables, so
no new data is introduced. The declaration carries the canonical encoding
name, a UTF-16/32 stream is preceded by a byte-order mark, and any
unrepresentable scalar becomes a decimal numeric character reference, so
output round-trips. CJK output encodings remain (tracked on #106).

Part of #106, #105
2026-06-09 09:32:39 +02:00
87fd7ba889 feat(emitting): textEscaping round-trip option to preserve carriage returns (#91) 2026-06-09 03:48:20 +02:00
e2ba6a7359 feat(emitting): cdataAsText and asciiOnly save options
- cdataAsText emits a CDATA section's content as escaped text instead of
  a <![CDATA[ ... ]]> section.
- asciiOnly escapes every non-ASCII character as a numeric character
  reference, so the output is pure ASCII (libxml2's character-reference
  encoding).

Both apply to the Serializer and the incremental Writer through the
shared Escaping rules.

Closes #91
2026-06-08 16:34:57 +02:00
cd2f30a262 feat(emitting): attribute quote style, line endings, xml:space, writer namespaces
Broaden the serializer and writer options from the audit:

- attributeQuote selects single or double quotes; the active quote is
  escaped in values (apostrophe as &apos; in single-quote mode).
- lineEnding controls the inserted newline (set to CRLF for \r\n output).
- xml:space=preserve suppresses pretty-print indentation for an element
  and its subtree; xml:space=default re-enables it.
- Writer gains writeStartElementNS(prefix:localName:namespaceURI:) and
  writeNamespace(prefix:uri:), so callers no longer hand-format prefixes
  and xmlns attributes.

CDATA-vs-escape and entity re-escaping strategies are left as a
follow-up; the current escaping is the safe default.

Closes #91
2026-06-08 15:24:49 +02:00
6b12319c89 feat(emit): XML declaration save-option parity
Add includeXMLDeclaration, xmlVersion, encodingName, and standalone to
Emitting.Options, with a single shared xmlDeclaration computation consumed
by both the tree serializer and the incremental writer. Writer gains
writeStartDocument(). Defaults stay off so fragment output is unchanged.

Closes #34
2026-06-08 02:57:27 +02:00
9b55767b39 feat(emitting): add an incremental XML writer (xmlTextWriter)
Add PureXML.Emitting.Writer, the libxml2 xmlTextWriter model: emit a document
with writeStartElement/writeAttribute/writeString/writeEndElement and friends,
never building a tree. Escaping is extracted into a shared Emitting.Escaping
helper used by both the writer and the serializer, so compact writer output is
byte-identical to serializing the equivalent tree. Pretty mode indents element
children (forward-only, so prefer compact for mixed content). Closes #32.
Reimplemented from the libxml2 model; no upstream code copied. 6 tests; 90 total.
2026-06-08 02:32:39 +02:00
209b6f1538 refactor(emitter): make the serializer iterative and fix mixed content
Reimplement the serializer as an explicit work-stack walk instead of recursion,
mirroring libxml2's xmlNodeDumpOutputInternal, so it is no longer the depth
bottleneck. Reimplemented in Swift; no upstream code copied.

Also fix a mixed-content correctness bug: formatting is now suppressed for any
element with text or CDATA children (previously only when ALL children were
text), so pretty-printing never injects whitespace around significant text.

Note surfaced while testing: the remaining depth ceiling is the recursive
Model.Node indirect enum (recursive ARC teardown overflows at extreme depth),
which is why the parser must cap nesting depth (next).
2026-06-08 00:31:05 +02:00
5a7a52b000 fix(emitter): escape tab/newline/CR and > in attribute values
The serializer escaped only & < " in attribute values, so an attribute holding
tab, newline, or carriage return would not survive a spec-compliant parser's
attribute-value normalization. Match the libxml2 attribute escape table: emit
&#9; &#10; &#13; for the whitespace characters and &gt; for >. Behavior derived
from the libxml2 analysis (xmlsave.c / xmlIO.c escape tables), reimplemented in
Swift; no upstream code copied.
2026-06-08 00:20:55 +02:00
708390242c chore: scaffold PureXML pure-Swift XML package
Root Swift package mirroring PureYAML's structure, rules, and verification
gates. Includes the XML node model (Node, Element, Attribute, QualifiedName),
a working emitter (Serializer) with escaping and pretty/compact options, and
structural validation. The parser surface is stable; parse() raises
notImplemented until the scanner lands, pinned by a test.

- dependency-free, namespaced under PureXML
- portability and style gates carried over (style, namespacing, forbidden
  patterns, swiftformat, swiftlint, linux, wasm, windows CI)
- 11 tests passing
2026-06-07 23:20:20 +02:00