Skip to content

Commit ed3fe0a

Browse files
committed
Add sourceOffset and make other minor improvements.
1 parent aa830c0 commit ed3fe0a

File tree

6 files changed

+61
-50
lines changed

6 files changed

+61
-50
lines changed

README.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Then, include Nodal in the target dependencies and make sure to enable C++ inter
3131
```swift
3232
let document = Document()
3333
let root = document.makeDocumentElement(name: "content", defaultNamespace: "http://tomasf.se/xml/example")
34-
let entry = root.appendElement("entry")
34+
let entry = root.addElement("entry")
3535
entry[attribute: "key"] = "price"
3636
entry.appendText("499")
3737
let output = try document.xmlData()
@@ -59,7 +59,7 @@ XPath is a language for querying and selecting nodes from an XML document. It al
5959

6060
### Example Usage
6161
```swift
62-
let document = try Document(xmlString: """
62+
let document = try Document(string: """
6363
<catalog>
6464
<book id="bk101">
6565
<title>XML Developer's Guide</title>
@@ -70,17 +70,17 @@ let document = try Document(xmlString: """
7070
</catalog>
7171
""")
7272

73-
if let name = query.firstNodeResult(with: document)?.node?.concatenatedText {
73+
if let name = query.firstNodeResult(with: document)?.node?.textContent {
7474
print("Book name:", name) // Outputs "XML Developer's Guide"
7575
}
7676
```
7777

78-
While Nodal supports XML namespaces when working with the DOM, its XPath implementation does not support XML namespaces. This limitation arises from pugixml, which lacks namespace support. To work with elements and attributes in documents that use namespaces, you must use their qualified names in your XPath expressions.
78+
While Nodal supports XML namespaces when working with the DOM, its XPath implementation does not support namespaces. This limitation arises from pugixml. To work with elements and attributes in documents that use namespaces, you must use their qualified names in your XPath expressions.
7979

8080
## Contributions
8181

8282
Contributions are welcome! If you have ideas, suggestions, or improvements, feel free to open an issue or submit a pull request.
8383

8484
## License
8585

86-
This project is licensed under the MIT License. See the LICENSE file for details.
86+
This project is licensed under the MIT License, and so is pugixml. See the respective LICENSE files for details.

Sources/Nodal/Document/Document+DocElement.swift

+12-12
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,22 @@ internal extension Document {
1212
}
1313

1414
public extension Document {
15-
/// The root element of the document, or `nil` if the document does not have a root element.
15+
/// The document (root) element of the document, or `nil` if the document does not have a document element.
1616
///
17-
/// - Note: The root element is the top-level element in the document tree.
17+
/// - Note: The document element is the top-level element in the document tree.
1818
var documentElement: Element? {
1919
let root = pugiDocument.__document_elementUnsafe()
2020
return root.empty() ? nil : element(for: root)
2121
}
2222

23-
/// Creates a new root element for the document with the specified name and optional default namespace URI.
23+
/// Creates a new document (root) element for the document with the specified name and optional default namespace URI.
2424
///
2525
/// - Parameters:
26-
/// - name: The name of the new root element.
27-
/// - uri: The default namespace URI to associate with the root element. Defaults to `nil`.
28-
/// - Returns: The newly created root element.
26+
/// - name: The name of the new document element.
27+
/// - uri: The default namespace URI to associate with the document element. Defaults to `nil`.
28+
/// - Returns: The newly created element.
2929
///
30-
/// - Note: If the document already has a root element, it is removed before creating the new one.
30+
/// - Note: If the document already has a document element, it is removed before creating the new one.
3131
func makeDocumentElement(name: String, defaultNamespace uri: String? = nil) -> Element {
3232
let element = clearDocumentElement()
3333
element.name = name
@@ -37,14 +37,14 @@ public extension Document {
3737
return element
3838
}
3939

40-
/// Creates a new root element for the document using an expanded name and declares a namespace for a prefix.
40+
/// Creates a new document (root) element for the document using an expanded name and declares a namespace for a prefix.
4141
///
4242
/// - Parameters:
43-
/// - name: The expanded name of the new root element, which includes a local name and an optional namespace.
44-
/// - prefix: The prefix to declare for the namespace associated with the root element.
45-
/// - Returns: The newly created root element.
43+
/// - name: The expanded name of the new document element, which includes a local name and an optional namespace.
44+
/// - prefix: The prefix to declare for the namespace associated with the document element.
45+
/// - Returns: The newly created element.
4646
///
47-
/// - Note: If the document already has a root element, it is removed before creating the new one.
47+
/// - Note: If the document already has a document element, it is removed before creating the new one.
4848
func makeDocumentElement(name: ExpandedName, declaringNamespaceFor prefix: String) -> Element {
4949
let element = clearDocumentElement()
5050
if let uri = name.namespaceName {

Sources/Nodal/Document/Document+Input.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ public extension Document {
4646
/// - Throws: `ParseError` if the parsing fails due to malformed XML, file access issues, or other errors.
4747
///
4848
/// - Note: This initializer reads the file from the provided URL and builds the corresponding document tree.
49-
convenience init(url: URL, encoding: String.Encoding? = nil, options: ParseOptions = .default) throws(ParseError) {
49+
convenience init(url fileURL: URL, encoding: String.Encoding? = nil, options: ParseOptions = .default) throws(ParseError) {
5050
self.init()
51-
let result = url.withUnsafeFileSystemRepresentation { path in
51+
let result = fileURL.withUnsafeFileSystemRepresentation { path in
5252
pugiDocument.load_file(path, options.rawValue, encoding?.pugiEncoding ?? pugi.encoding_auto)
5353
}
5454
if result.status != pugi.status_ok {

Sources/Nodal/Document/Error.swift

+30-29
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ import pugixml
33

44
public extension Document {
55
/// Represents an error that occurs during the parsing of an XML document.
6-
struct ParseError: Error {
6+
struct ParseError: Error, LocalizedError {
77
/// The reason for the parse failure.
88
public let reason: Reason
99

10+
/// A description of the error that occured.
11+
public let description: String
12+
1013
/// The character offset in the input where the error occurred.
1114
public let offset: Int
1215

@@ -17,6 +20,7 @@ public extension Document {
1720
self.reason = .init(result.status)
1821
self.offset = result.offset
1922
self.encoding = .init(result.encoding)
23+
self.description = String(cString: result.description())
2024
}
2125

2226
/// Represents the specific reason for a parse failure.
@@ -25,43 +29,40 @@ public extension Document {
2529
case fileNotFound
2630

2731
/// An I/O error occurred while reading the file or stream.
28-
case ioError
32+
case readError
2933

3034
/// Memory allocation failed during parsing.
3135
case outOfMemory
3236

33-
/// An internal parser error occurred.
34-
case internalError
37+
/// The parser encountered a malformed tag name.
38+
case badTagName
3539

36-
/// The parser encountered an unrecognized tag.
37-
case unrecognizedTag
40+
/// A start element tag was malformed.
41+
case badStartElement
42+
43+
/// An end element tag was malformed.
44+
case badEndElement
45+
46+
/// A mismatch occurred between a start tag and an end tag.
47+
case endElementMismatch
48+
49+
/// An attribute was malformed.
50+
case badAttribute
3851

3952
/// A processing instruction or document declaration was malformed.
4053
case badProcessingInstruction
4154

4255
/// A comment was malformed.
4356
case badComment
4457

58+
/// A text section was malformed.
59+
case badText
60+
4561
/// A CDATA section was malformed.
46-
case badCData
62+
case badCDATA
4763

4864
/// A document type declaration was malformed.
49-
case badDoctype
50-
51-
/// A PCDATA section was malformed.
52-
case badPCDATA
53-
54-
/// A start element tag was malformed.
55-
case badStartElement
56-
57-
/// An attribute was malformed.
58-
case badAttribute
59-
60-
/// An end element tag was malformed.
61-
case badEndElement
62-
63-
/// A mismatch occurred between a start tag and an end tag.
64-
case endElementMismatch
65+
case badDOCTYPE
6566

6667
/// An attempt was made to append nodes to an invalid root type.
6768
case appendInvalidRoot
@@ -75,21 +76,21 @@ public extension Document {
7576
internal init(_ pugiStatus: pugi.xml_parse_status) {
7677
switch pugiStatus {
7778
case pugi.status_file_not_found: self = .fileNotFound
78-
case pugi.status_io_error: self = .ioError
79+
case pugi.status_io_error: self = .readError
7980
case pugi.status_out_of_memory: self = .outOfMemory
80-
case pugi.status_internal_error: self = .internalError
81-
case pugi.status_unrecognized_tag: self = .unrecognizedTag
81+
case pugi.status_unrecognized_tag: self = .badTagName
8282
case pugi.status_bad_pi: self = .badProcessingInstruction
8383
case pugi.status_bad_comment: self = .badComment
84-
case pugi.status_bad_cdata: self = .badCData
85-
case pugi.status_bad_doctype: self = .badDoctype
86-
case pugi.status_bad_pcdata: self = .badPCDATA
84+
case pugi.status_bad_cdata: self = .badCDATA
85+
case pugi.status_bad_doctype: self = .badDOCTYPE
86+
case pugi.status_bad_pcdata: self = .badText
8787
case pugi.status_bad_start_element: self = .badStartElement
8888
case pugi.status_bad_attribute: self = .badAttribute
8989
case pugi.status_bad_end_element: self = .badEndElement
9090
case pugi.status_end_element_mismatch: self = .endElementMismatch
9191
case pugi.status_append_invalid_root: self = .appendInvalidRoot
9292
case pugi.status_no_document_element: self = .noDocumentElement
93+
case pugi.status_internal_error: self = .unknown
9394
default: self = .unknown
9495
}
9596
}

Sources/Nodal/Node/Node.swift

+12-1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,17 @@ public extension Node {
130130
node.set_value(newValue)
131131
}
132132
}
133+
134+
/// The offset of this node from the beginning of the original XML buffer, if available.
135+
///
136+
/// This property provides the offset in characters from the start of the XML data used to parse the document.
137+
/// The offset is only available if he node was parsed from a stream or buffer and has not undergone significant changes since parsing.
138+
///
139+
/// - Returns: The character offset from the beginning of the XML buffer, or `nil` if unavailable.
140+
var sourceOffset: Int? {
141+
let offset = node.offset_debug()
142+
return offset == -1 ? nil : offset
143+
}
133144
}
134145

135146
internal extension Node {
@@ -151,7 +162,7 @@ extension Node: CustomDebugStringConvertible {
151162
case .text: "Text \"\(value)\""
152163
case .cdata: "CDATA \"\(value)\""
153164
case .comment: "Comment <!--\(value)-->"
154-
case .doctype: "DOCTYPE <!DOCTYPE \(value)>"
165+
case .doctype: "Document type declaration <!DOCTYPE \(value)>"
155166
case .processingInstruction: "PI <?\(name) \(value)?>"
156167
case .declaration: "Declaration <?\(name)...?>"
157168
case .document: "Document"

Sources/Tests/Tests.swift

-1
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,5 @@ struct Tests {
163163
#expect(Array(root.children) == [b, a], "Order of children")
164164
let c = root.addCDATA("c", at: .after(b))
165165
#expect(Array(root.children) == [b, c, a], "Order of children")
166-
167166
}
168167
}

0 commit comments

Comments
 (0)