11# Changelog
22
3- Itemised from v0.3.9 onward; for earlier releases see the
4- [ GitHub releases] ( https://github.com/JuliaData/XML.jl/releases ) and git tags.
5- Format follows [ Keep a Changelog] ( https://keepachangelog.com/en/1.1.0/ ) .
3+ All notable changes to XML.jl will be documented in this file.
4+
5+ The format is based on [ Keep a Changelog] ( https://keepachangelog.com/en/1.1.0/ ) ,
6+ and this project adheres to [ Semantic Versioning] ( https://semver.org/spec/v2.0.0.html ) .
7+
8+ ## [ 0.4.0] - 2026-07-03
9+
10+ > ** Upgrading from 0.3.x?** See the standalone [ v0.4 migration guide] ( MIGRATING_TO_v0.4.md ) .
11+
12+ ### Added
13+ - New streaming tokenizer (` XMLTokenizer ` module) for fine-grained XML token iteration.
14+ - Pull/cursor streaming API — ` Cursor ` with ` next! ` , ` for_each_child ` / ` @for_each_child ` ,
15+ ` skip_element! ` , and ` eof ` for forward, allocation-light traversal of large documents ([ #8 ] , [ #61 ] ).
16+ - XPath support via ` xpath(node, path) ` — an experimental subset of XPath 1.0 ([ #30 ] ).
17+ - Configurable well-formedness: ` parse ` /` read ` accept ` wellformed = :lenient | :structural | :strict `
18+ (default ` :structural ` ).
19+ - ` get(node, key, default) ` accessor, matching ` getindex ` ([ #50 ] ).
20+ - ` test/test_libxml2_testcases.jl ` : 243 test cases borrowed from the [ libxml2] ( https://github.com/GNOME/libxml2 ) test suite.
21+ - ` AbstractTrees ` package extension (` print_tree ` , ` PreOrderDFS ` , ` Leaves ` , … on ` Node ` and ` LazyNode ` ).
22+
23+ ### Changed
24+ - ** ` Node ` is now parametric ` Node{S} ` ** (storage type ` S ` , typically ` String ` or ` SubString{String} ` )
25+ and is no longer a subtype of ` AbstractXMLNode ` . ` ::Node ` annotations continue to work.
26+ - ** A ` Node ` 's ` attributes ` field is now a ` Vector{Pair{S,S}} ` ** (was ` OrderedDict{String,String} ` ).
27+ Use the ` attributes(node) ` accessor — which returns an ordered ` Attributes <: AbstractDict ` , or
28+ ` nothing ` — instead of indexing the field directly.
29+ - ** ` LazyNode ` is now an immutable ` LazyNode{S} ` ** constructed with ` parse(x, LazyNode) ` /
30+ ` read(file, LazyNode) ` ; the ` .raw ` field is removed and accessors return ` SubString ` views.
31+ - ** ` XML.write ` now escapes text and attribute values automatically** , and ` parse ` /` read ` unescape
32+ into values — so ` value(node) ` returns decoded text (` & ` , not ` & ` ). Round-trips are preserved
33+ because ` write ` re-escapes.
34+ - ** Duplicate attribute names now raise an error** during parsing.
35+ - ** ` parse ` /` read ` reject malformed documents by default** (` wellformed = :structural ` ): multiple
36+ root elements, a document with no root element, non-whitespace text outside the root, empty/invalid
37+ element names, a literal ` < ` in an attribute value, and a misplaced/duplicate/nested DOCTYPE or XML
38+ declaration. ` :strict ` additionally rejects ` -- ` (or a trailing ` - ` ) in comments, empty/invalid PI
39+ targets, and characters — numeric references * and* raw literal characters — outside the XML §2.2
40+ ` Char ` range. Pass ` wellformed = :lenient ` to restore the previous permissive behavior; note that a
41+ standalone DTD file or a prolog-only fragment (no root element) now needs ` :lenient ` .
42+ - ** ` Node ` constructors validate names and content.** Element/PI names must be valid XML names, and
43+ ` Comment ` /` CData ` /` ProcessingInstruction ` content may not contain its own close delimiter (` --> ` ,
44+ ` ]]> ` , ` ?> ` ), which would otherwise split the node on write. (Content is not otherwise validated —
45+ e.g. a ` Comment ` whose text contains ` -- ` is constructed as-is and is rejected only on re-parse at
46+ ` :strict ` .)
47+ - ** ` escape ` is no longer idempotent** — every ` & ` is escaped, so ` escape("&") == "&amp;" ` ;
48+ call it only on raw, unescaped text ([ #52 ] ).
49+ - ** ` read ` no longer memory-maps** the input file.
50+ - ** Minimum Julia version is now 1.10.**
51+
52+ ### Deprecated
53+ - ` simplevalue ` — already a deprecated alias of ` simple_value ` * before* 0.4 — is ** no longer exported** (it stays
54+ reachable as ` XML.simplevalue ` , still warning). Use ` simple_value ` . * (Not a new 0.4 deprecation: the rename to the
55+ snake_case ` simple_value ` , matching ` is_simple ` / ` is_simple_value ` , predates this release; 0.4 only un-exports the
56+ old alias.)*
57+
58+ ### Removed
59+ - ** ` XML.Raw ` ** and the Raw/LazyNode streaming internals — use ` parse(x, Node) ` / ` read(file, Node) `
60+ for an in-memory tree, or the new ` Cursor ` API for streaming.
61+ - ** ` next ` / ` prev ` ** (LazyNode traversal), and ** ` prev! ` ** (the 0.3.x in-place ` LazyNode ` advance) —
62+ parse to a ` Node ` and use ` children ` / integer indexing, or the ` Cursor ` API. (` next! ` still exists
63+ but now advances a ` Cursor ` , not a ` LazyNode ` ; there is no ` prev! ` — ` Cursor ` is forward-only.)
64+ - ** Single-argument ` parent(node) ` / ` depth(node) ` ** — use ` parent(child, root) ` / ` depth(child, root) ` .
65+ - ** ` nodes_equal(a, b) ` ** — use ` a == b ` .
66+ - ** ` escape! ` / ` unescape! ` ** — escaping/unescaping now happens automatically in ` write ` / ` parse ` .
67+ - ** ` DTDBody ` ** — use ` parse_dtd ` or the ` DTD ` node.
68+
69+ (Calling a removed function throws an error whose message names the replacement; ` DTDBody ` is
70+ removed outright and raises ` UndefVarError ` . These removals are all consequences of the v0.4 internals
71+ rewrite ([ #54 ] ); none has a dedicated issue to link individually.)
72+
73+ ### Fixed
74+ - ** Tokenizer: multi-byte UTF-8 in attribute values** — values like ` <doc city="東京"/> ` no longer
75+ raise ` StringIndexError ` (` attr_value() ` used byte arithmetic instead of ` prevind ` ).
76+ - ** Tokenizer: quotes inside DTD comments** — a ` " ` /` ' ` inside a ` <!-- --> ` comment in a DTD internal
77+ subset no longer triggers an "Unterminated quoted string" error.
78+ - Numeric character references are now escaped/unescaped correctly ([ #17 ] ).
79+ - ` unescape ` no longer double-unescapes; each reference is processed exactly once ([ #53 ] ). It is now
80+ single-pass, so a numeric reference resolving to ` & ` is never re-scanned as a named entity —
81+ ` unescape("&amp;") ` is ` "&" ` , not ` "&" ` .
82+ - A leading U+FEFF byte-order-mark character in an in-memory string is now stripped by
83+ ` parse(_, LazyNode) ` and ` Cursor ` as well as ` parse(_, Node) ` , so all three readers agree.
84+ - A truncated comment/CDATA/PI/DOCTYPE at end of input raises a clear "unterminated …" error instead
85+ of being silently accepted (` Node ` ) or crashing ` value() ` (` LazyNode ` /` Cursor ` ).
86+ - Odd-length UTF-16 input that begins with a BOM raises a clear error instead of an opaque
87+ ` reinterpret ` failure.
88+ - Processing-instruction content keeps its trailing whitespace on round-trip (only the leading
89+ separator after the target is dropped, per §2.6).
90+ - ` parse_dtd ` reports a clear error on parameter-entity references (` %name; ` ) instead of an opaque
91+ internal error.
92+ - XPath: unsupported axis syntax (` child:: ` , ` descendant:: ` , …) now raises instead of silently
93+ returning the wrong result; dead scaffolding in ` xpath ` was removed.
694
795## [ 0.3.9] - 2026-06-20
896
@@ -19,8 +107,155 @@ First release since XML.jl moved to [JuliaData](https://github.com/JuliaData/XML
19107### Changed
20108- Relaxed the OrderedCollections.jl compat bound to include v2 ([ #64 ] ).
21109
110+ ## [ 0.3.8]
111+
112+ ### Fixed
113+ - ` XML.write ` now respects ` xml:space="preserve" ` and suppresses indentation for elements with this attribute ([ #49 ] ).
114+
115+ ## [ 0.3.7]
116+
117+ ### Fixed
118+ - Resolved remaining issues from [ #45 ] and fixed [ #46 ] (whitespace preservation edge cases) ([ #47 ] ).
119+
120+ ## [ 0.3.6]
121+
122+ ### Added
123+ - ` XML.write ` respects ` xml:space="preserve" ` on elements, suppressing automatic indentation ([ #45 ] ).
124+
125+ ### Fixed
126+ - ` String ` type ambiguity on Julia nightly resolved ([ #38 ] ).
127+
128+ ## [ 0.3.5]
129+
130+ ### Fixed
131+ - ` depth ` and ` parent ` functions corrected to work properly with the DOM tree API ([ #37 ] ).
132+ - ` escape ` updated to no longer be idempotent — every ` & ` is now escaped, matching spec behavior ([ #32 ] , addressing [ #31 ] ).
133+ - ` pushfirst! ` support added for ` Node ` children ([ #29 ] ).
134+
135+ ## [ 0.3.4]
136+
137+ ### Fixed
138+ - Fixed [ #26 ] .
139+ - CI updated to use ` julia-actions/cache@v4 ` and ` lts ` Julia version.
140+
141+ ## [ 0.3.3]
142+
143+ ### Added
144+ - ` h ` constructor for concise element creation (e.g., ` h.div("hello"; class="main") ` ).
145+
146+ ### Fixed
147+ - Path definition error in README example ([ #20 ] ).
148+
149+ ## [ 0.3.2]
150+
151+ ### Fixed
152+ - Minor typos.
153+
154+ ## [ 0.3.1]
155+
156+ ### Added
157+ - Julia 1.6 compatibility ([ #16 ] ).
158+
159+ ### Changed
160+ - Smarter escaping logic.
161+
162+ ## [ 0.3.0]
163+
164+ ### Changed
165+ - Attribute internal representation changed from ` Dict ` to ` OrderedDict ` (later reverted to ` Vector{Pair} ` ).
166+
167+ ## [ 0.2.3]
168+
169+ ### Fixed
170+ - Parse method fix.
171+
172+ ## [ 0.2.2]
173+
174+ ### Added
175+ - DTD parsing via ` parse_dtd ` .
176+ - ` is_simple ` and ` simple_value ` exports.
177+ - ` setindex! ` methods for modifying attributes.
178+ - ` unescape ` function.
179+
180+ ### Fixed
181+ - DOCTYPE parsing made case-insensitive.
182+
183+ ## [ 0.2.1]
184+
185+ ### Fixed
186+ - Write output fixes.
187+
188+ ## [ 0.2.0]
189+
190+ ### Changed
191+ - Major rewrite: introduced ` NodeType ` enum, ` Node{S} ` parametric struct, callable ` NodeType ` constructors, and ` XML.write ` .
192+ - Processing instruction support.
193+ - Benchmarks added.
194+
195+ ## [ 0.1.3]
196+
197+ ### Changed
198+ - Improved print output for ` AbstractXMLNode ` .
199+
200+ ## [ 0.1.2]
201+
202+ ### Added
203+ - AbstractTrees 0.4 compatibility ([ #5 ] ).
204+
205+ ## [ 0.1.1]
206+
207+ ### Added
208+ - ` Node ` implementation with ` print_tree ` .
209+ - Color output in REPL display.
210+ - Stopped stripping whitespace from text nodes.
211+
212+ ## [ 0.1.0]
213+
214+ - Initial release.
215+
216+ [ Unreleased ] : https://github.com/JuliaData/XML.jl/compare/v0.3.9...HEAD
22217[ 0.3.9 ] : https://github.com/JuliaData/XML.jl/compare/v0.3.8...v0.3.9
218+ [ 0.3.8 ] : https://github.com/JuliaData/XML.jl/compare/v0.3.7...v0.3.8
219+ [ 0.3.7 ] : https://github.com/JuliaData/XML.jl/compare/v0.3.6...v0.3.7
220+ [ 0.3.6 ] : https://github.com/JuliaData/XML.jl/compare/v0.3.5...v0.3.6
221+ [ 0.3.5 ] : https://github.com/JuliaData/XML.jl/compare/v0.3.4...v0.3.5
222+ [ 0.3.4 ] : https://github.com/JuliaData/XML.jl/compare/v0.3.3...v0.3.4
223+ [ 0.3.3 ] : https://github.com/JuliaData/XML.jl/compare/v0.3.2...v0.3.3
224+ [ 0.3.2 ] : https://github.com/JuliaData/XML.jl/compare/v0.3.1...v0.3.2
225+ [ 0.3.1 ] : https://github.com/JuliaData/XML.jl/compare/v0.3.0...v0.3.1
226+ [ 0.3.0 ] : https://github.com/JuliaData/XML.jl/compare/v0.2.3...v0.3.0
227+ [ 0.2.3 ] : https://github.com/JuliaData/XML.jl/compare/v0.2.2...v0.2.3
228+ [ 0.2.2 ] : https://github.com/JuliaData/XML.jl/compare/v0.2.1...v0.2.2
229+ [ 0.2.1 ] : https://github.com/JuliaData/XML.jl/compare/v0.2.0...v0.2.1
230+ [ 0.2.0 ] : https://github.com/JuliaData/XML.jl/compare/v0.1.3...v0.2.0
231+ [ 0.1.3 ] : https://github.com/JuliaData/XML.jl/compare/v0.1.2...v0.1.3
232+ [ 0.1.2 ] : https://github.com/JuliaData/XML.jl/compare/v0.1.1...v0.1.2
233+ [ 0.1.1 ] : https://github.com/JuliaData/XML.jl/compare/v0.1.0...v0.1.1
234+ [ 0.1.0 ] : https://github.com/JuliaData/XML.jl/releases/tag/v0.1.0
235+
236+ [ #5 ] : https://github.com/JuliaData/XML.jl/pull/5
237+ [ #16 ] : https://github.com/JuliaData/XML.jl/pull/16
238+ [ #20 ] : https://github.com/JuliaData/XML.jl/pull/20
239+ [ #26 ] : https://github.com/JuliaData/XML.jl/issues/26
240+ [ #29 ] : https://github.com/JuliaData/XML.jl/pull/29
241+ [ #31 ] : https://github.com/JuliaData/XML.jl/issues/31
242+ [ #32 ] : https://github.com/JuliaData/XML.jl/pull/32
243+ [ #37 ] : https://github.com/JuliaData/XML.jl/pull/37
244+ [ #38 ] : https://github.com/JuliaData/XML.jl/pull/38
245+ [ #43 ] : https://github.com/JuliaData/XML.jl/issues/43
246+ [ #45 ] : https://github.com/JuliaData/XML.jl/pull/45
247+ [ #46 ] : https://github.com/JuliaData/XML.jl/issues/46
248+ [ #47 ] : https://github.com/JuliaData/XML.jl/pull/47
249+ [ #49 ] : https://github.com/JuliaData/XML.jl/pull/49
23250[ #56 ] : https://github.com/JuliaData/XML.jl/pull/56
24251[ #59 ] : https://github.com/JuliaData/XML.jl/pull/59
25252[ #60 ] : https://github.com/JuliaData/XML.jl/pull/60
26253[ #64 ] : https://github.com/JuliaData/XML.jl/pull/64
254+ [ #8 ] : https://github.com/JuliaData/XML.jl/issues/8
255+ [ #17 ] : https://github.com/JuliaData/XML.jl/issues/17
256+ [ #30 ] : https://github.com/JuliaData/XML.jl/issues/30
257+ [ #50 ] : https://github.com/JuliaData/XML.jl/issues/50
258+ [ #52 ] : https://github.com/JuliaData/XML.jl/issues/52
259+ [ #53 ] : https://github.com/JuliaData/XML.jl/issues/53
260+ [ #54 ] : https://github.com/JuliaData/XML.jl/pull/54
261+ [ #61 ] : https://github.com/JuliaData/XML.jl/issues/61
0 commit comments