Skip to content

Commit

Permalink
Move filter support to XMLIndexer
Browse files Browse the repository at this point in the history
Related to #174
  • Loading branch information
drmohundro committed Feb 3, 2018
1 parent 079d823 commit c3406cf
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 117 deletions.
105 changes: 41 additions & 64 deletions Source/SWXMLHash.swift
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,43 @@ public enum XMLIndexer {
}
}

public func filter(_ included: (_ elem: XMLElement, _ index: Int) -> Bool) -> XMLIndexer {
switch self {
case .list(let list):
let results = filterWithIndex(seq: list, included: included)
if results.count == 1 {
return XMLIndexer.element(results.first!)
}
return XMLIndexer.list(results)

case .element(let elem):
return XMLIndexer.list(filterWithIndex(seq: elem.xmlChildren, included: included))

case .stream(let ops):
let found = ops.findElements()

let list: [XMLElement]
if found.all.count == 1 {
list = found.children.map { $0.element! }
} else {
list = found.all.map { $0.element! }
}
let results = filterWithIndex(seq: list, included: included)
if results.count == 1 {
return XMLIndexer.element(results.first!)
}
return XMLIndexer.list(results)

default:
return XMLIndexer.list([])
}
}

private func filterWithIndex(seq: [XMLElement],
included: (_ elem: XMLElement, _ index: Int) -> Bool) -> [XMLElement] {
return zip(seq.indices, seq).filter { included($1, $0) }.map { $1 }
}

/// All child elements from the currently indexed level
public var children: [XMLIndexer] {
var list = [XMLIndexer]()
Expand Down Expand Up @@ -857,7 +894,7 @@ public class XMLElement: XMLContent {
var index: Int
let options: SWXMLHashOptions

public var xmlChildren: [XMLElement] {
var xmlChildren: [XMLElement] {
return children.flatMap { $0 as? XMLElement }
}

Expand All @@ -868,73 +905,13 @@ public class XMLElement: XMLContent {
- name: The name of the element to be initialized
- index: The index of the element to be initialized
- options: The SWXMLHash options
- children: (Optional) children
- allAttributes: (Optional) attributes
*/
init(name: String, index: Int = 0, options: SWXMLHashOptions, children: [XMLContent] = [], allAttributes: [String: XMLAttribute] = [:]) {
init(name: String, index: Int = 0, options: SWXMLHashOptions) {
self.name = name
self.index = index
self.options = options
self.children = children
self.allAttributes = allAttributes
}

public func filter(from start: Int, to end: Int) -> XMLIndexer {

let subrangeChildren: [XMLContent] = children.enumerated().filter({
offset, element in

return start <= offset && offset <= end

}).map({$0.element})

let elem = XMLElement(name: name, index: index, options: options, children: subrangeChildren, allAttributes: allAttributes)

return XMLIndexer.element(elem)

}

public func filter(_ isIncluded: (XMLContent) -> Bool) -> XMLIndexer {

let subrangeChildren: [XMLContent] = children.filter(isIncluded)
let elem = XMLElement(name: name, index: index, options: options, children: subrangeChildren, allAttributes: allAttributes)

return XMLIndexer.element(elem)

}

/**
Initialize a XMLIndexer based on this XMLElement instance and
a subrange of it's (xml)children
- parameters:
- from: Start index for a subrange of this XMLElement's instance children
- to: End index for a subrange of this XMLElement's instance children
- filterByXmlChildren: Use only children of type XMLElement for the new indexer
*/
@available(*, deprecated)
public func indexer(from start: Int, to end: Int, filterByXmlChildren: Bool) throws -> XMLIndexer {

let sourceChildren: [XMLContent] = filterByXmlChildren ? xmlChildren : children

guard 0 <= start && start < sourceChildren.count else {
return XMLIndexer.xmlError(IndexingError.index(idx: start))
}

guard 0 <= end && end < sourceChildren.count else {
return XMLIndexer.xmlError(IndexingError.index(idx: end))
}

guard start <= end else {
return XMLIndexer.xmlError(IndexingError.index(idx: start))
}

let subrangeChildren: [XMLContent] = Array(sourceChildren[start ... end])
let elem = XMLElement(name: name, index: index, options: options, children: subrangeChildren, allAttributes: allAttributes)

return XMLIndexer.element(elem)

}

}

/**
Adds a new XMLElement underneath this instance of XMLElement
Expand Down
17 changes: 16 additions & 1 deletion Tests/SWXMLHashTests/LazyXMLParsingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,20 @@ class LazyXMLParsingTests: XCTestCase {
func testShouldReturnNilWhenKeysDontMatch() {
XCTAssertNil(xml!["root"]["what"]["header"]["foo"].element?.name)
}

func testShouldBeAbleToFilterOnIndexer() {
let subIndexer = xml!["root"]["catalog"]["book"]
.filter { elem, _ in elem.attribute(by: "id")!.text == "bk102" }
.filter { _, index in index >= 1 && index <= 3 }

XCTAssertEqual(subIndexer[0].element?.name, "title")
XCTAssertEqual(subIndexer[1].element?.name, "genre")
XCTAssertEqual(subIndexer[2].element?.name, "price")

XCTAssertEqual(subIndexer[0].element?.text, "Midnight Rain")
XCTAssertEqual(subIndexer[1].element?.text, "Fantasy")
XCTAssertEqual(subIndexer[2].element?.text, "5.95")
}
}

extension LazyXMLParsingTests {
Expand All @@ -158,7 +172,8 @@ extension LazyXMLParsingTests {
("testShouldBeAbleToHandleMixedContent", testShouldBeAbleToHandleMixedContent),
("testShouldHandleInterleavingXMLElements", testShouldHandleInterleavingXMLElements),
("testShouldBeAbleToProvideADescriptionForTheDocument", testShouldBeAbleToProvideADescriptionForTheDocument),
("testShouldReturnNilWhenKeysDontMatch", testShouldReturnNilWhenKeysDontMatch)
("testShouldReturnNilWhenKeysDontMatch", testShouldReturnNilWhenKeysDontMatch),
("testShouldBeAbleToFilterOnIndexer", testShouldBeAbleToFilterOnIndexer)
]
}
}
87 changes: 35 additions & 52 deletions Tests/SWXMLHashTests/XMLParsingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -244,63 +244,47 @@ class XMLParsingTests: XCTestCase {
}
XCTAssertNotNil(err)
}

func testShouldBeAbleToCreateASubIndexer() {

let element: XMLElement = xml!["root"]["catalog"]["book"][1].element!
let subIndexer: XMLIndexer = try! element.indexer(from: 1, to: 3, filterByXmlChildren: true)

XCTAssertEqual(subIndexer.children[0].element?.name, "title")
XCTAssertEqual(subIndexer.children[1].element?.name, "genre")
XCTAssertEqual(subIndexer.children[2].element?.name, "price")

XCTAssertEqual(subIndexer.children[0].element?.text, "Midnight Rain")
XCTAssertEqual(subIndexer.children[1].element?.text, "Fantasy")
XCTAssertEqual(subIndexer.children[2].element?.text, "5.95")
let subIndexer = xml!["root"]["catalog"]["book"][1].filter { _, index in index >= 1 && index <= 3 }

XCTAssertEqual(subIndexer[0].element?.name, "title")
XCTAssertEqual(subIndexer[1].element?.name, "genre")
XCTAssertEqual(subIndexer[2].element?.name, "price")

XCTAssertEqual(subIndexer[0].element?.text, "Midnight Rain")
XCTAssertEqual(subIndexer[1].element?.text, "Fantasy")
XCTAssertEqual(subIndexer[2].element?.text, "5.95")
}

func testShouldBeAbleToCreateASubIndexerFromFilter() {

let element: XMLElement = xml!["root"]["catalog"]["book"][1].element!
let filterByNames: [String] = ["title", "genre", "price"]

let subIndexer: XMLIndexer = element.filter({
element in

guard let xmlElement = element as? XMLElement else {
return false
}

return filterByNames.contains(xmlElement.name)

})

XCTAssertEqual(subIndexer.children[0].element?.name, "title")
XCTAssertEqual(subIndexer.children[1].element?.name, "genre")
XCTAssertEqual(subIndexer.children[2].element?.name, "price")

XCTAssertEqual(subIndexer.children[0].element?.text, "Midnight Rain")
XCTAssertEqual(subIndexer.children[1].element?.text, "Fantasy")
XCTAssertEqual(subIndexer.children[2].element?.text, "5.95")

let subIndexer = xml!["root"]["catalog"]["book"][1].filter { elem, _ in
let filterByNames = ["title", "genre", "price"]
return filterByNames.contains(elem.name)
}

XCTAssertEqual(subIndexer[0].element?.name, "title")
XCTAssertEqual(subIndexer[1].element?.name, "genre")
XCTAssertEqual(subIndexer[2].element?.name, "price")

XCTAssertEqual(subIndexer[0].element?.text, "Midnight Rain")
XCTAssertEqual(subIndexer[1].element?.text, "Fantasy")
XCTAssertEqual(subIndexer[2].element?.text, "5.95")
}

func testShouldBeAbleToCreateASubIndexerFromFilterAndIndexes() {

let element: XMLElement = xml!["root"]["catalog"]["book"][1].element!
let subIndexer: XMLIndexer = element.filter({$0 is XMLElement}).element!.filter(from: 1, to: 3)

XCTAssertEqual(subIndexer.children[0].element?.name, "title")
XCTAssertEqual(subIndexer.children[1].element?.name, "genre")
XCTAssertEqual(subIndexer.children[2].element?.name, "price")

XCTAssertEqual(subIndexer.children[0].element?.text, "Midnight Rain")
XCTAssertEqual(subIndexer.children[1].element?.text, "Fantasy")
XCTAssertEqual(subIndexer.children[2].element?.text, "5.95")


func testShouldBeAbleToFilterOnIndexer() {
let subIndexer = xml!["root"]["catalog"]["book"]
.filter { elem, _ in elem.attribute(by: "id")!.text == "bk102" }
.filter { _, index in index >= 1 && index <= 3 }

XCTAssertEqual(subIndexer[0].element?.name, "title")
XCTAssertEqual(subIndexer[1].element?.name, "genre")
XCTAssertEqual(subIndexer[2].element?.name, "price")

XCTAssertEqual(subIndexer[0].element?.text, "Midnight Rain")
XCTAssertEqual(subIndexer[1].element?.text, "Fantasy")
XCTAssertEqual(subIndexer[2].element?.text, "5.95")
}

}

extension XMLParsingTests {
Expand All @@ -327,9 +311,8 @@ extension XMLParsingTests {
("testShouldProvideAnErrorObjectWhenKeysDontMatch", testShouldProvideAnErrorObjectWhenKeysDontMatch),
("testShouldProvideAnErrorElementWhenIndexersDontMatch", testShouldProvideAnErrorElementWhenIndexersDontMatch),
("testShouldStillReturnErrorsWhenAccessingViaSubscripting", testShouldStillReturnErrorsWhenAccessingViaSubscripting),
("testShouldBeAbleToCreateASubIndexer", testShouldBeAbleToCreateASubIndexer),
("testShouldBeAbleToCreateASubIndexerFromFilter", testShouldBeAbleToCreateASubIndexerFromFilter),
("testShouldBeAbleToCreateASubIndexerFromFilterAndIndexes", testShouldBeAbleToCreateASubIndexerFromFilterAndIndexes)
("testShouldBeAbleToFilterOnIndexer", testShouldBeAbleToFilterOnIndexer)
]
}
}

0 comments on commit c3406cf

Please sign in to comment.