Skip to content

Perfect-XML 4.0.0

Latest

Choose a tag to compare

@taplin taplin released this 13 Oct 14:19
c019dad

The first release since 3.1.3 (2018). It brings the package up to Swift 6, drops the separate Perfect-libxml2 dependency, keeps external entities out of parsed documents whatever libxml2's global defaults are, and fixes a crash on non-ASCII input in the SAX parser. It is a major version: the toolchain floor rises to Swift 6.2 and a few APIs changed. Read "Breaking and behaviour changes" before upgrading.

Requirements

  • Swift tools 6.2 (// swift-tools-version: 6.2); 3.1.3 declared 4.0.
  • Platforms: macOS 12+ and iOS 15+ (both newly declared; 3.1.3 declared none), and Linux.
  • No external Swift package dependencies. On macOS and iOS, libxml2 comes from the SDK and nothing needs installing. On Linux, install libxml2-dev and pkg-config.
.package(url: "https://github.com/PerfectlySoft/Perfect-XML.git", from: "4.0.0")

CI now runs on GitHub Actions for every push and pull request: Linux (Swift 6.2 and 6.4) and macOS (Xcode 26.0.1 and 26.6), each in debug and release, plus the tests on an iOS Simulator (the library builds at the iOS 15 deployment target), with warnings treated as errors.

Highlights

  • Swift 6 (#6). The library builds cleanly in the Swift 6 language mode. The document, node, SAXParser and XMLStream classes are @unchecked Sendable, each with a comment explaining why.
  • libxml2 is wrapped inside the package (#6). A .systemLibrary target in this repo replaces the Perfect-libxml2 package. The C module keeps its name, perfectxml2. On Apple platforms the target uses the SDK's libxml2 without pkg-config, so a Homebrew libxml2 on PKG_CONFIG_PATH can no longer mix its headers with the SDK's and fail the build with "conflicting types" errors.
  • iOS. The package builds for iOS and its tests pass on the iOS Simulator (Xcode 27 and Xcode 26.3), so iOS 15 is now declared.
  • External entities are never loaded. XDocument builds its own parser context and forces entity substitution and external DTD loading off, so <!ENTITY x SYSTEM "file:///…"> is never read, even if something called xmlSubstituteEntitiesDefault(1) (a libxml2 global; per-thread in threaded builds) on the thread the parse runs on. 3.1.3's xmlParseDoc honoured that global and would substitute the file's contents; with default globals, neither 3.1.3 nor 4.0.0 loads them. XMLStream forces the same reader properties off.
  • Swift Testing. The test suite moved from XCTest to Swift Testing (30 tests).

Breaking and behaviour changes

  • Toolchain and platforms. Swift tools 6.2 is required, with macOS 12 or iOS 15 at minimum. Upgrade: use a Swift 6.2 or newer toolchain and raise your deployment targets to at least macOS 12 / iOS 15.
  • Perfect-libxml2 is no longer a dependency (#6). Upgrade: nothing for most users. If your own package listed Perfect-libxml2 only for Perfect-XML's sake, remove it. On macOS, a Homebrew libxml2 is no longer needed; don't brew link --force libxml2 for this package.
  • Sendable classes (#6). The classes are @unchecked Sendable but have no internal locking. Upgrade: use each document, SAXParser or XMLStream from one task at a time.
  • XNode.ownerDocument is read-only (#6); it is now public private(set). Upgrade: stop assigning to it.
  • XMLStream no longer loads external DTDs (#6). 3.1.3 OR'd XML_PARSER_SUBST_ENTITIES into the reader options. That constant belongs to a different libxml2 enum, and its value equals XML_PARSE_DTDLOAD, so it turned DTD loading on. Upgrade: nothing, unless you depended on DTD defaults being applied in the stream.
  • Entity substitution is forced off. Code that turned libxml2's xmlSubstituteEntitiesDefault global on (per-thread in threaded builds) used to get entities substituted into XDocument trees as text; entity references now stay in the tree. Upgrade: nodeValue still returns the expanded text of internal entities, so most code is unaffected. External entities are no longer loaded at all; if you relied on that, read the file yourself.
  • XMLEncoder throws instead of crashing (#6). Encoding an unsupported shape (an unkeyed or single-value container, or a nested Encodable) now throws XMLEncoderError instead of calling fatalError. A top-level array also throws, including an empty one, which used to encode silently as <root/>. Upgrade: handle the error; encode was already throws.
  • XMLEncoder namespace: writes a namespace declaration. 3.1.3 wrote <root:urn:example>…</root>, which isn't well-formed XML. It now writes <root xmlns="urn:example">…</root>. Upgrade: if you post-processed the broken output, stop.
  • XMLDecoderError is removed. Nothing threw it; there is no XML decoder. Upgrade: delete references to it.

Fixes

  • XDocument no longer substitutes file:// external entities when libxml2's substitution global is on; tests cover default globals and the global turned on. Parsing also passes XML_PARSE_NONET (#6) as defence in depth; 3.1.3's XMLStream already did.
  • #6: The streaming reader's options no longer enable DTD loading by mistake.
  • #6: SAXParser no longer traps on non-ASCII text (any byte of 0x80 and above): the string conversion its callbacks use built Int8 values from UInt8 bytes. There is a regression test pushing café 你好 😀 through the SAX parser. XMLStream wasn't affected.
  • #6: HTMLDocument no longer builds a mutable pointer from a temporary array (a dangling-pointer pattern the compiler flagged), and the SAX and stream callbacks use scoped buffer pointers.
  • #6: The XPath error handler no longer prints "help" to standard output.
  • #6: XMLEncoder throws on unsupported shapes instead of fatalError.
  • XMLEncoder namespace: output is well-formed, and a top-level empty array throws instead of encoding as an empty root.
  • #6: On macOS, a Homebrew libxml2 on PKG_CONFIG_PATH no longer breaks the build.
  • The Linux system-library target gains a yum provider (libxml2-devel), and the stale Perfect Assistant PADockerfile is gone.
  • README corrections. The macOS build notes told readers to install and force-link Homebrew's libxml2, which is the setup that breaks the build; the Codable section described XML decoding, which doesn't exist (only XMLEncoder does).

Upgrade notes

  1. Point your dependency at from: "4.0.0" and build with Swift 6.2 or newer; raise deployment targets to macOS 12 / iOS 15.
  2. On Linux, make sure libxml2-dev and pkg-config are installed. On macOS, nothing is needed, and you can remove a Homebrew libxml2 you installed only for this package.
  3. Remove any assignment to XNode.ownerDocument.
  4. If you parse documents that rely on external DTDs, check the results: XMLStream no longer loads them.
  5. If you use XMLEncoder, handle XMLEncoderError for arrays and nested values, and check any consumer of namespace: output. Remove references to XMLDecoderError.
  6. Keep each parsed document and parser on one task at a time.

Legacy

The pre-Swift-6 code is preserved on the legacy branch, which points at the same commit as the 3.1.3 tag. Stay on 3.1.3 if you can't move to Swift 6.2 yet.