Skip to content
This repository was archived by the owner on Oct 17, 2021. It is now read-only.
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
11 changes: 9 additions & 2 deletions Sources/DOM/Element.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ public final class Element: Node {
}

set {
xmlSetProp(xmlNode, attribute, newValue)
if let newValue = newValue {
xmlSetProp(xmlNode, attribute, newValue)
} else {
xmlUnsetProp(xmlNode, attribute)
}
}
}

Expand Down Expand Up @@ -79,7 +83,10 @@ public final class Element: Node {

// MARK: -

public required init?(rawValue: UnsafeMutableRawPointer) {
public required init?(rawValue: UnsafeMutableRawPointer?) {
guard let rawValue = rawValue else {
return nil
}
guard rawValue.bindMemory(to: _xmlNode.self, capacity: 1).pointee.type == XML_ELEMENT_NODE else { return nil }
super.init(rawValue: rawValue)
}
Expand Down
64 changes: 53 additions & 11 deletions Sources/DOM/Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ open class Node: RawRepresentable, Equatable, Hashable, CustomStringConvertible

public var content: String? {
get {
return String(cString: xmlNodeGetContent(xmlNode))
return String(xmlString: xmlNodeGetContent(xmlNode))
}

set {
Expand All @@ -30,7 +30,7 @@ open class Node: RawRepresentable, Equatable, Hashable, CustomStringConvertible

public var xpath: String? {
guard let cString = xmlGetNodePath(xmlNode) else { return nil }
return String(cString: cString)
return String(xmlString: cString)
}

func unlink() {
Expand Down Expand Up @@ -69,36 +69,61 @@ open class Node: RawRepresentable, Equatable, Hashable, CustomStringConvertible
// MARK: -

public protocol Constructable {
static func construct(with rawValue: xmlNodePtr) -> Node?
static func construct(with rawValue: xmlNodePtr?) -> Node?
}

extension Constructable where Self: Node {

public var children: [Node] {
guard let firstChild = xmlNode.pointee.children else { return [] }
return sequence(first: firstChild, next: { $0.pointee.next })
.compactMap { Self.construct(with: $0) }
}


public var firstChildElement: Element? {
return findFirstNode(start: xmlNode.pointee.children, next: { $0.pointee.next }) { node in
node as? Element != nil
} as? Element
}

public func firstChildElement(named name: String) -> Element? {
for case let element as Element in children where element.name == name {
return element
}

return nil
return findFirstNode(start: xmlNode.pointee.children, next: { $0.pointee.next }) { node in
(node as? Element)?.name == name
} as? Element
}


public var lastChildElement: Element? {
return findFirstNode(start: lastChild?.xmlNode, next: { $0.pointee.prev }) { node in
(node as? Element) != nil
} as? Element
}

public func lastChildElement(named name: String) -> Element? {
return findFirstNode(start: lastChild?.xmlNode, next: { $0.pointee.prev }) { node in
(node as? Element)?.name == name
} as? Element
}

public var parent: Element? {
return Element(rawValue: xmlNode.pointee.parent)
}

public var previous: Node? {
return Self.construct(with: xmlNode.pointee.prev)
}

public var next: Node? {
return Self.construct(with: xmlNode.pointee.next)
}

public var firstChild: Node? {
return Self.construct(with: xmlNode.pointee.children)
}

public var lastChild: Node? {
return Self.construct(with: xmlGetLastChild(xmlNode))
}

@discardableResult
public func unwrap() -> Node? {
let children = sequence(first: xmlNode.pointee.children, next: { $0.pointee.next }).compactMap { Node(rawValue: $0) }
Expand All @@ -118,4 +143,21 @@ extension Constructable where Self: Node {

return children.first
}

private func findFirstNode(
start: UnsafeMutablePointer<_xmlNode>?,
next: @escaping (xmlNodePtr) -> xmlNodePtr?,
where predicate: (Node) -> Bool ) -> Node?
{
var n = start
while let each = n {
if let node = Self.construct(with: each) {
if predicate(node) {
return node
}
}
n = next(each)
}
return nil
}
}
6 changes: 5 additions & 1 deletion Sources/HTML/Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import libxml2
import DOM

extension Node: Constructable {
public static func construct(with rawValue: xmlNodePtr) -> Node? {
public static func construct(with rawValue: xmlNodePtr?) -> Node? {
guard let rawValue = rawValue else {
return nil
}

switch rawValue.pointee.type {
case XML_ELEMENT_NODE:
return Element(rawValue: rawValue)
Expand Down
12 changes: 10 additions & 2 deletions Sources/XML/Element.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,17 @@ extension Element {

set {
if let namespace = namespace {
xmlSetNsProp(xmlNode, namespace.rawValue, attribute, newValue)
if let newValue = newValue {
xmlSetNsProp(xmlNode, namespace.rawValue, attribute, newValue)
} else {
xmlUnsetNsProp(xmlNode, namespace.rawValue, attribute)
}
} else {
xmlSetProp(xmlNode, attribute, newValue)
if let newValue = newValue {
xmlSetProp(xmlNode, attribute, newValue)
} else {
xmlUnsetProp(xmlNode, attribute)
}
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion Sources/XML/Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import libxml2
import DOM

extension Node: Constructable {
public static func construct(with rawValue: xmlNodePtr) -> Node? {
public static func construct(with rawValue: xmlNodePtr?) -> Node? {
guard let rawValue = rawValue else {
return nil
}

switch rawValue.pointee.type {
case XML_ELEMENT_NODE:
return Element(rawValue: rawValue)
Expand Down
31 changes: 31 additions & 0 deletions Tests/XMLTests/XMLTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,35 @@ final class XMLTests: XCTestCase {
XCTAssertEqual(results?.count, 1)
XCTAssertEqual(results?.first?["name"], "h1")
}

func testTraverse() throws {
let xml = #"""
<root>
hello
<one/>
<two/>
<three/>
world
</root>
"""#

let doc = try XML.Document(string: xml, options: [.removeBlankNodes])!
let root = doc.root!
XCTAssertEqual(root.name, "root")
XCTAssertNil(root.next)
XCTAssertNil(root.previous)
XCTAssertEqual(root.firstChildElement?.name, "one")
XCTAssertEqual(root.lastChildElement?.name, "three")
XCTAssertEqual(root.firstChild?.content?.trimmingCharacters(in: .whitespacesAndNewlines), "hello")
XCTAssertEqual(root.lastChild?.content?.trimmingCharacters(in: .whitespacesAndNewlines), "world")
XCTAssertNil(root.firstChild?.firstChild)
XCTAssertNil(root.firstChild?.lastChild)
XCTAssertNil(root.firstChildElement?.firstChild)
XCTAssertNil(root.firstChildElement?.lastChild)
}

func testParent() throws {
XCTAssertNil(Element(name: "test").parent)
}

}