Skip to content

Commit eaff6d9

Browse files
committed
Parity: XMLElement.normalizeAdjacentTextNodes…
- Implement it like Darwin does. - Nodes representing CDATA sections now return .kind == .text, like on Darwin. - .setChildren() now correctly replaces all children, not just the first. - .childCount now returns correct results. Fixes https://bugs.swift.org/browse/SR-10425
1 parent d602c57 commit eaff6d9

File tree

7 files changed

+94
-4
lines changed

7 files changed

+94
-4
lines changed

CoreFoundation/Parsing.subproj/CFXMLInterface.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ CFIndex _kCFXMLTypeAttribute = XML_ATTRIBUTE_NODE;
6262
CFIndex _kCFXMLTypeProcessingInstruction = XML_PI_NODE;
6363
CFIndex _kCFXMLTypeComment = XML_COMMENT_NODE;
6464
CFIndex _kCFXMLTypeText = XML_TEXT_NODE;
65+
CFIndex _kCFXMLTypeCDataSection = XML_CDATA_SECTION_NODE;
6566
CFIndex _kCFXMLTypeDTD = XML_DTD_NODE;
6667
CFIndex _kCFXMLDocTypeHTML = XML_DOC_HTML;
6768
CFIndex _kCFXMLTypeNamespace = 22; // libxml2 does not define namespaces as nodes, so we have to fake it

CoreFoundation/Parsing.subproj/CFXMLInterface.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ extern CFIndex _kCFXMLTypeAttribute;
5353
extern CFIndex _kCFXMLTypeProcessingInstruction;
5454
extern CFIndex _kCFXMLTypeComment;
5555
extern CFIndex _kCFXMLTypeText;
56+
extern CFIndex _kCFXMLTypeCDataSection;
5657
extern CFIndex _kCFXMLTypeDTD;
5758
extern CFIndex _kCFXMLDocTypeHTML;
5859
extern CFIndex _kCFXMLTypeNamespace;

Foundation/XMLDTD.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ open class XMLDTD : XMLNode {
9898
}
9999
}
100100

101+
open override var childCount: Int {
102+
return _CFXMLNodeGetElementChildCount(_xmlNode)
103+
}
104+
101105
/*!
102106
@method insertChild:atIndex:
103107
@abstract Inserts a child at a particular index.

Foundation/XMLDocument.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,10 @@ open class XMLDocument : XMLNode {
267267

268268
return XMLNode._objectNodeForNode(rootPtr) as? XMLElement
269269
}
270+
271+
open override var childCount: Int {
272+
return _CFXMLNodeGetElementChildCount(_xmlNode)
273+
}
270274

271275
/*!
272276
@method insertChild:atIndex:

Foundation/XMLElement.swift

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,45 @@ open class XMLElement: XMLNode {
323323
@method normalizeAdjacentTextNodesPreservingCDATA:
324324
@abstract Adjacent text nodes are coalesced. If the node's value is the empty string, it is removed. This should be called with a value of NO before using XQuery or XPath.
325325
*/
326-
open func normalizeAdjacentTextNodesPreservingCDATA(_ preserve: Bool) { NSUnimplemented() }
326+
open func normalizeAdjacentTextNodesPreservingCDATA(_ preserve: Bool) {
327+
// Replicate Darwin behavior: no change occurs at all in this case.
328+
guard childCount != 1 else { return }
329+
330+
var text = ""
331+
var index = 0
332+
let count = childCount
333+
var children: [XMLNode] = []
334+
335+
while index < count {
336+
let child = self.children![index]
337+
let isText = child.kind == .text
338+
let isCDataToPreserve = preserve ? (isText && child.isCData) : false
339+
340+
if isText && !isCDataToPreserve {
341+
if let stringValue = child.stringValue {
342+
text.append(contentsOf: stringValue)
343+
}
344+
} else {
345+
if !text.isEmpty {
346+
let mergedText = XMLNode.text(withStringValue: text) as! XMLNode
347+
children.append(mergedText)
348+
text = ""
349+
}
350+
if child.kind == .element, let child = child as? XMLElement {
351+
child.normalizeAdjacentTextNodesPreservingCDATA(preserve)
352+
}
353+
children.append(child)
354+
}
355+
356+
index += 1
357+
}
358+
359+
if !text.isEmpty {
360+
children.append(XMLNode.text(withStringValue: text) as! XMLNode)
361+
}
362+
363+
self.setChildren(children)
364+
}
327365

328366
internal override class func _objectNodeForNode(_ node: _CFXMLNodePtr) -> XMLElement {
329367
precondition(_CFXMLNodeGetType(node) == _kCFXMLTypeElement)

Foundation/XMLNode.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ open class XMLNode: NSObject, NSCopying {
315315
case _kCFXMLTypeComment:
316316
return .comment
317317

318+
case _kCFXMLTypeCDataSection: fallthrough
318319
case _kCFXMLTypeText:
319320
return .text
320321

@@ -323,6 +324,10 @@ open class XMLNode: NSObject, NSCopying {
323324
}
324325
}
325326

327+
internal var isCData: Bool {
328+
return _CFXMLNodeGetType(_xmlNode) == _kCFXMLTypeCDataSection
329+
}
330+
326331
/*!
327332
@method name
328333
@abstract Sets the nodes name. Applicable for element, attribute, namespace, processing-instruction, document type declaration, element declaration, attribute declaration, entity declaration, and notation declaration.
@@ -446,8 +451,8 @@ open class XMLNode: NSObject, NSCopying {
446451
internal func _removeAllChildren() {
447452
var nextChild = _CFXMLNodeGetFirstChild(_xmlNode)
448453
while let child = nextChild {
449-
_CFXMLUnlinkNode(child)
450454
nextChild = _CFXMLNodeGetNextSibling(child)
455+
_CFXMLUnlinkNode(child)
451456
}
452457
_childNodes.removeAll(keepingCapacity: true)
453458
}
@@ -558,10 +563,10 @@ open class XMLNode: NSObject, NSCopying {
558563

559564
/*!
560565
@method childCount
561-
@abstract The amount of children, relevant for documents, elements, and document type declarations. Use this instead of [[self children] count].
566+
@abstract The amount of children, relevant for documents, elements, and document type declarations.
562567
*/
563568
open var childCount: Int {
564-
return _CFXMLNodeGetElementChildCount(_xmlNode)
569+
return self.children?.count ?? 0
565570
}
566571

567572
/*!

TestFoundation/TestXMLDocument.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,42 @@ class TestXMLDocument : LoopbackServerTest {
685685
XCTAssertEqual(dtd.systemID, plistDTDUrl)
686686
}
687687

688+
func test_parsingCDataSections() throws {
689+
let xmlString = """
690+
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
691+
<content>some text <![CDATA[Some verbatim content! <br> Yep, it's HTML, what are you going to do]]> some more text</content>
692+
"""
693+
694+
do {
695+
let doc = try XMLDocument(xmlString: xmlString, options: [])
696+
697+
let root = try XCTUnwrap(doc.rootElement())
698+
XCTAssertEqual(root.childCount, 3)
699+
700+
root.normalizeAdjacentTextNodesPreservingCDATA(false)
701+
XCTAssertEqual(root.childCount, 1)
702+
XCTAssertEqual(root.children?.first?.stringValue, "some text Some verbatim content! <br> Yep, it's HTML, what are you going to do some more text")
703+
}
704+
705+
do {
706+
let doc = try XMLDocument(xmlString: xmlString, options: [])
707+
708+
let root = try XCTUnwrap(doc.rootElement())
709+
XCTAssertEqual(root.childCount, 3)
710+
711+
let prefix = XMLNode.text(withStringValue: "prefix! ") as! XMLNode
712+
root.insertChild(prefix, at: 0)
713+
print(root.children!.map { (name: $0.name, kind: $0.kind, stringValue: $0.stringValue ?? "") }.map { String(describing: $0) }.joined(separator: "\n"))
714+
715+
root.normalizeAdjacentTextNodesPreservingCDATA(true)
716+
XCTAssertEqual(root.childCount, 3)
717+
let children = try XCTUnwrap(root.children)
718+
XCTAssertEqual(children[0].stringValue, "prefix! some text ")
719+
XCTAssertEqual(children[1].stringValue, "Some verbatim content! <br> Yep, it's HTML, what are you going to do")
720+
XCTAssertEqual(children[2].stringValue, " some more text")
721+
}
722+
}
723+
688724
static var allTests: [(String, (TestXMLDocument) -> () throws -> Void)] {
689725
return [
690726
("test_basicCreation", test_basicCreation),
@@ -719,6 +755,7 @@ class TestXMLDocument : LoopbackServerTest {
719755
("test_nodeNames", test_nodeNames),
720756
("test_creatingAnEmptyDocumentAndNode", test_creatingAnEmptyDocumentAndNode),
721757
("test_creatingAnEmptyDTD", test_creatingAnEmptyDTD),
758+
("test_parsingCDataSections", test_parsingCDataSections),
722759
]
723760
}
724761
}

0 commit comments

Comments
 (0)