PureXML/Sources/Parsing/Diagnostic.swift
Mihaela Mihaljevic 9168f990d6 feat(parsing): surface the XML declaration (version, encoding, standalone)
Parse the XML declaration's pseudo-attributes instead of skipping the
<?xml?> processing instruction:

- version, encoding, and standalone are surfaced on a new XMLDeclaration,
  returned from parseWithDocumentType and via PureXML.xmlDeclaration(_:).
- The pseudo-attributes are validated: fixed order (version, encoding,
  standalone), known names only, and standalone must be yes or no. A
  malformed declaration is rejected.
- The declared version is now readable, a prerequisite for XML 1.1.

parseWithDocumentType returns a ParsedDocument struct (node,
documentType, declaration) rather than a 2-tuple; the member names are
unchanged so existing call sites are unaffected.

Closes #95
2026-06-08 14:53:04 +02:00

51 lines
2.2 KiB
Swift

public extension PureXML.Parsing {
/// A located problem found while reading a document. Unlike a thrown
/// ``ParseError``, a diagnostic does not abort the read: the recovering reader
/// records it, repairs as best it can, and continues, so an invalid document
/// still yields a best-effort tree alongside the list of what went wrong.
struct Diagnostic: Equatable, Sendable, CustomStringConvertible {
/// A human-readable, already-located description of the problem.
public var message: String
/// The source position, when known.
public var mark: Mark?
public init(message: String, mark: Mark? = nil) {
self.message = message
self.mark = mark
}
public var description: String {
message
}
}
}
extension PureXML.Parsing.Diagnostic {
/// Wraps a thrown ``PureXML/Parsing/ParseError`` as a diagnostic, keeping its
/// located message and structured position.
init(_ error: PureXML.Parsing.ParseError) {
self.init(message: error.description, mark: error.mark)
}
}
public extension PureXML.Parsing.ParseError {
/// The source position the error occurred at, when the case carries one.
var mark: PureXML.Parsing.Mark? {
switch self {
case .emptyDocument, .malformedEncoding, .notImplemented:
nil
case let .unexpectedEndOfInput(mark), let .unterminatedTag(mark), let .unterminatedComment(mark),
let .unterminatedCDATA(mark), let .expectedName(mark), let .expectedEquals(mark),
let .unquotedAttributeValue(mark), let .amplificationLimitExceeded(mark),
let .junkAfterDocumentElement(mark), let .unsupportedDoctype(mark), let .malformedDeclaration(mark):
mark
case let .unexpectedCharacter(_, mark), let .mismatchedEndTag(_, _, mark),
let .duplicateAttribute(_, mark), let .invalidReference(_, mark),
let .undefinedEntity(_, mark), let .recursiveEntity(_, mark),
let .unexpectedEndTag(_, mark), let .undefinedNamespacePrefix(_, mark),
let .nestingTooDeep(_, mark), let .nameTooLong(_, mark), let .contentTooLong(_, mark):
mark
}
}
}