Skip to content

Parity: XMLElement.normalizeAdjacentTextNodes… #2483

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CoreFoundation/Parsing.subproj/CFXMLInterface.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ CFIndex _kCFXMLTypeAttribute = XML_ATTRIBUTE_NODE;
CFIndex _kCFXMLTypeProcessingInstruction = XML_PI_NODE;
CFIndex _kCFXMLTypeComment = XML_COMMENT_NODE;
CFIndex _kCFXMLTypeText = XML_TEXT_NODE;
CFIndex _kCFXMLTypeCDataSection = XML_CDATA_SECTION_NODE;
CFIndex _kCFXMLTypeDTD = XML_DTD_NODE;
CFIndex _kCFXMLDocTypeHTML = XML_DOC_HTML;
CFIndex _kCFXMLTypeNamespace = 22; // libxml2 does not define namespaces as nodes, so we have to fake it
Expand Down
1 change: 1 addition & 0 deletions CoreFoundation/Parsing.subproj/CFXMLInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ extern CFIndex _kCFXMLTypeAttribute;
extern CFIndex _kCFXMLTypeProcessingInstruction;
extern CFIndex _kCFXMLTypeComment;
extern CFIndex _kCFXMLTypeText;
extern CFIndex _kCFXMLTypeCDataSection;
extern CFIndex _kCFXMLTypeDTD;
extern CFIndex _kCFXMLDocTypeHTML;
extern CFIndex _kCFXMLTypeNamespace;
Expand Down
4 changes: 4 additions & 0 deletions Foundation/XMLDTD.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ open class XMLDTD : XMLNode {
}
}

open override var childCount: Int {
return _CFXMLNodeGetElementChildCount(_xmlNode)
}

/*!
@method insertChild:atIndex:
@abstract Inserts a child at a particular index.
Expand Down
4 changes: 4 additions & 0 deletions Foundation/XMLDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ open class XMLDocument : XMLNode {

return XMLNode._objectNodeForNode(rootPtr) as? XMLElement
}

open override var childCount: Int {
return _CFXMLNodeGetElementChildCount(_xmlNode)
}

/*!
@method insertChild:atIndex:
Expand Down
40 changes: 39 additions & 1 deletion Foundation/XMLElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,45 @@ open class XMLElement: XMLNode {
@method normalizeAdjacentTextNodesPreservingCDATA:
@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.
*/
open func normalizeAdjacentTextNodesPreservingCDATA(_ preserve: Bool) { NSUnimplemented() }
open func normalizeAdjacentTextNodesPreservingCDATA(_ preserve: Bool) {
// Replicate Darwin behavior: no change occurs at all in this case.
guard childCount != 1 else { return }

var text = ""
var index = 0
let count = childCount
var children: [XMLNode] = []

while index < count {
let child = self.children![index]
let isText = child.kind == .text
let isCDataToPreserve = preserve ? (isText && child.isCData) : false

if isText && !isCDataToPreserve {
if let stringValue = child.stringValue {
text.append(contentsOf: stringValue)
}
} else {
if !text.isEmpty {
let mergedText = XMLNode.text(withStringValue: text) as! XMLNode
children.append(mergedText)
text = ""
}
if child.kind == .element, let child = child as? XMLElement {
child.normalizeAdjacentTextNodesPreservingCDATA(preserve)
}
children.append(child)
}

index += 1
}

if !text.isEmpty {
children.append(XMLNode.text(withStringValue: text) as! XMLNode)
}

self.setChildren(children)
}

internal override class func _objectNodeForNode(_ node: _CFXMLNodePtr) -> XMLElement {
precondition(_CFXMLNodeGetType(node) == _kCFXMLTypeElement)
Expand Down
11 changes: 8 additions & 3 deletions Foundation/XMLNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ open class XMLNode: NSObject, NSCopying {
case _kCFXMLTypeComment:
return .comment

case _kCFXMLTypeCDataSection: fallthrough
case _kCFXMLTypeText:
return .text

Expand All @@ -323,6 +324,10 @@ open class XMLNode: NSObject, NSCopying {
}
}

internal var isCData: Bool {
return _CFXMLNodeGetType(_xmlNode) == _kCFXMLTypeCDataSection
}

/*!
@method name
@abstract Sets the nodes name. Applicable for element, attribute, namespace, processing-instruction, document type declaration, element declaration, attribute declaration, entity declaration, and notation declaration.
Expand Down Expand Up @@ -446,8 +451,8 @@ open class XMLNode: NSObject, NSCopying {
internal func _removeAllChildren() {
var nextChild = _CFXMLNodeGetFirstChild(_xmlNode)
while let child = nextChild {
_CFXMLUnlinkNode(child)
nextChild = _CFXMLNodeGetNextSibling(child)
_CFXMLUnlinkNode(child)
}
_childNodes.removeAll(keepingCapacity: true)
}
Expand Down Expand Up @@ -558,10 +563,10 @@ open class XMLNode: NSObject, NSCopying {

/*!
@method childCount
@abstract The amount of children, relevant for documents, elements, and document type declarations. Use this instead of [[self children] count].
@abstract The amount of children, relevant for documents, elements, and document type declarations.
*/
open var childCount: Int {
return _CFXMLNodeGetElementChildCount(_xmlNode)
return self.children?.count ?? 0
}

/*!
Expand Down
37 changes: 37 additions & 0 deletions TestFoundation/TestXMLDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,42 @@ class TestXMLDocument : LoopbackServerTest {
XCTAssertEqual(dtd.systemID, plistDTDUrl)
}

func test_parsingCDataSections() throws {
let xmlString = """
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<content>some text <![CDATA[Some verbatim content! <br> Yep, it's HTML, what are you going to do]]> some more text</content>
"""

do {
let doc = try XMLDocument(xmlString: xmlString, options: [])

let root = try XCTUnwrap(doc.rootElement())
XCTAssertEqual(root.childCount, 3)

root.normalizeAdjacentTextNodesPreservingCDATA(false)
XCTAssertEqual(root.childCount, 1)
XCTAssertEqual(root.children?.first?.stringValue, "some text Some verbatim content! <br> Yep, it's HTML, what are you going to do some more text")
}

do {
let doc = try XMLDocument(xmlString: xmlString, options: [])

let root = try XCTUnwrap(doc.rootElement())
XCTAssertEqual(root.childCount, 3)

let prefix = XMLNode.text(withStringValue: "prefix! ") as! XMLNode
root.insertChild(prefix, at: 0)
print(root.children!.map { (name: $0.name, kind: $0.kind, stringValue: $0.stringValue ?? "") }.map { String(describing: $0) }.joined(separator: "\n"))

root.normalizeAdjacentTextNodesPreservingCDATA(true)
XCTAssertEqual(root.childCount, 3)
let children = try XCTUnwrap(root.children)
XCTAssertEqual(children[0].stringValue, "prefix! some text ")
XCTAssertEqual(children[1].stringValue, "Some verbatim content! <br> Yep, it's HTML, what are you going to do")
XCTAssertEqual(children[2].stringValue, " some more text")
}
}

static var allTests: [(String, (TestXMLDocument) -> () throws -> Void)] {
return [
("test_basicCreation", test_basicCreation),
Expand Down Expand Up @@ -719,6 +755,7 @@ class TestXMLDocument : LoopbackServerTest {
("test_nodeNames", test_nodeNames),
("test_creatingAnEmptyDocumentAndNode", test_creatingAnEmptyDocumentAndNode),
("test_creatingAnEmptyDTD", test_creatingAnEmptyDTD),
("test_parsingCDataSections", test_parsingCDataSections),
]
}
}
Expand Down