Tags: mmcdole/goxpp
Tags
goxpp v2: encapsulate parser state, rework errors, fix namespaces and… … xml:base (#39) Implements the v2 design from #34. The module path becomes github.com/mmcdole/goxpp/v2; the v1 branch carries the old API. State moves behind methods: XMLPullParser becomes Parser with no exported fields, so callers can no longer corrupt the depth, namespace and base stacks whose desync produced the #28 class of bugs. One constructor remains, New(*xml.Decoder); strictness and charset conversion are configured on the decoder. Errors: positional failures (Expect, ExpectAll, and the NextTag, NextText, Skip and DecodeElement preconditions) return *ExpectError with want/got fields and the input offset; decoder errors pass through for errors.As; advancement calls after EndDocument return io.EOF instead of succeeding forever; the sticky error is readable via Err(). Namespaces are tracked prefix to URI with case preserved, so duplicate bindings are no longer lossy; Namespaces() returns a snapshot and PrefixForURI() serves the reverse lookup with innermost-scope, most-recent-declaration semantics. xml:base keeps the scoped tracking but drops the resolution API: the trailing-slash directory heuristic violated RFC 3986 and gofeed had already forked the function. BaseURL() exposes the in-scope base; nested bases resolve per RFC 3986; an unparseable xml:base inherits the parent. Depth at an EndTag now reports the same depth as its matching StartTag, and the end tag's namespace scope and base remain visible until the next advancement, including for the end tag DecodeElement synthesizes. Deleted: the exported state fields, urlStack, CharsetReader, NewXMLPullParser, NewXMLPullParserWithDecoder, XmlBaseResolveUrl, EventName, the EventType method, IgnorableWhitespace. Tests are rewritten for the new API: 29 tests, 95.6% statement coverage, including the #28 poison reproduction, EOF after end of document, shadowed-prefix lookups, RFC 3986 base resolution, and the live-Attrs mutation contract gofeed depends on. Closes #34 Closes #30 Closes #32 Closes #33
Prefer un-namespaced attributes in Attribute lookup (#38) Attribute returned the first attribute matching the local name in document order, so a foreign-namespaced attribute (e.g. xlink:href) appearing before the plain one shadowed it. Prefer the un-namespaced attribute; keep returning a namespaced one when no plain attribute exists. Fixes #31 Claude-Session: https://claude.ai/code/session_014x6BJWjgnaUKYHdK8QCvqa
Fix BaseStack push/pop asymmetry (#25) pushBase only pushed when an element had its own xml:base, but processEndToken and DecodeElement popped unconditionally, so closing any element without its own base popped an ancestor's base off the stack, desyncing xml:base resolution for everything after it. Push a base entry for every element (its own resolved xml:base, or the parent's base inherited), matching the per-element pop. Mirrors how SpacesStack already works.
Remove testify; lower go directive to 1.19 (#23) goxpp is a small foundational library and testify was its only dependency. Replace its test assertions with a few stdlib helpers so the module has zero dependencies. Also lower the go directive from 1.22 (which was arbitrary, nothing here uses 1.20+ features) to 1.19, so consumers aren't forced onto a newer Go than they need.
Add test for absolute path as xml:base I wanted to prove to myself that the current xml:base implementation handles absolute paths with no domain part (as in "xml:base=/absolute") by replacing the current base path (as illustrate in the spec example: https://www.w3.org/TR/xmlbase/) All tests pass.
Keep track of the current xml:base value Tracks xml:base attributes in a stack of *url.URLs. Consumers of the parser can access the top-level URL through `XMLPullParser.BaseStack.Top()` This is useful for applications that need to resolve URLs in XML documents relative to the xml:base attributes. To that end, a helper method is provided which will resolve a relative string to an absolute URL according to the current base: `func (p *XMLPullParser) XmlBaseResolveUrl(u string) (*url.URL, error)` Includes a single test. It is not comprehensive, but it checks for xml:base to two levels, tests resolving a string against the current base, as well as resolution of relative xml:base values.