Repository navigation
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-devandpkg-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,
SAXParserandXMLStreamclasses are@unchecked Sendable, each with a comment explaining why. - libxml2 is wrapped inside the package (#6). A
.systemLibrarytarget 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 onPKG_CONFIG_PATHcan 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.
XDocumentbuilds its own parser context and forces entity substitution and external DTD loading off, so<!ENTITY x SYSTEM "file:///…">is never read, even if something calledxmlSubstituteEntitiesDefault(1)(a libxml2 global; per-thread in threaded builds) on the thread the parse runs on. 3.1.3'sxmlParseDochonoured that global and would substitute the file's contents; with default globals, neither 3.1.3 nor 4.0.0 loads them.XMLStreamforces 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 libxml2for this package. Sendableclasses (#6). The classes are@unchecked Sendablebut have no internal locking. Upgrade: use each document,SAXParserorXMLStreamfrom one task at a time.XNode.ownerDocumentis read-only (#6); it is nowpublic private(set). Upgrade: stop assigning to it.XMLStreamno longer loads external DTDs (#6). 3.1.3 OR'dXML_PARSER_SUBST_ENTITIESinto the reader options. That constant belongs to a different libxml2 enum, and its value equalsXML_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
xmlSubstituteEntitiesDefaultglobal on (per-thread in threaded builds) used to get entities substituted intoXDocumenttrees as text; entity references now stay in the tree. Upgrade:nodeValuestill 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. XMLEncoderthrows instead of crashing (#6). Encoding an unsupported shape (an unkeyed or single-value container, or a nestedEncodable) now throwsXMLEncoderErrorinstead of callingfatalError. A top-level array also throws, including an empty one, which used to encode silently as<root/>. Upgrade: handle the error;encodewas alreadythrows.XMLEncodernamespace: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.XMLDecoderErroris removed. Nothing threw it; there is no XML decoder. Upgrade: delete references to it.
Fixes
XDocumentno longer substitutesfile://external entities when libxml2's substitution global is on; tests cover default globals and the global turned on. Parsing also passesXML_PARSE_NONET(#6) as defence in depth; 3.1.3'sXMLStreamalready did.- #6: The streaming reader's options no longer enable DTD loading by mistake.
- #6:
SAXParserno longer traps on non-ASCII text (any byte of 0x80 and above): the string conversion its callbacks use builtInt8values fromUInt8bytes. There is a regression test pushingcafé 你好 😀through the SAX parser.XMLStreamwasn't affected. - #6:
HTMLDocumentno 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:
XMLEncoderthrows on unsupported shapes instead offatalError. XMLEncodernamespace: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_PATHno longer breaks the build. - The Linux system-library target gains a
yumprovider (libxml2-devel), and the stale Perfect AssistantPADockerfileis 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
XMLEncoderdoes).
Upgrade notes
- Point your dependency at
from: "4.0.0"and build with Swift 6.2 or newer; raise deployment targets to macOS 12 / iOS 15. - On Linux, make sure
libxml2-devandpkg-configare installed. On macOS, nothing is needed, and you can remove a Homebrew libxml2 you installed only for this package. - Remove any assignment to
XNode.ownerDocument. - If you parse documents that rely on external DTDs, check the results:
XMLStreamno longer loads them. - If you use
XMLEncoder, handleXMLEncoderErrorfor arrays and nested values, and check any consumer ofnamespace:output. Remove references toXMLDecoderError. - 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.