-
Notifications
You must be signed in to change notification settings - Fork 206
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: more breaking out of files
- Loading branch information
1 parent
8c6b0ce
commit 9ee0729
Showing
9 changed files
with
472 additions
and
278 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// | ||
// ParsingError.swift | ||
// SWXMLHash | ||
// | ||
// Copyright (c) 2022 David Mohundro | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. | ||
// | ||
|
||
import Foundation | ||
|
||
public struct ParsingError: Error { | ||
public let line: Int | ||
public let column: Int | ||
} | ||
|
||
extension ParsingError: CustomStringConvertible { | ||
public var description: String { | ||
"There was a parsing error on line: \(line), column: \(column)" | ||
} | ||
} | ||
|
||
extension ParsingError: LocalizedError { | ||
public var errorDescription: String? { | ||
"There was a parsing error on line: \(line), column: \(column)" | ||
} | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// | ||
// FullXMLParser.swift | ||
// SWXMLHash | ||
// | ||
// Copyright (c) 2014 David Mohundro | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. | ||
// | ||
|
||
import Foundation | ||
|
||
/// The implementation of XMLParserDelegate and where the parsing actually happens. | ||
class FullXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate { | ||
required init(_ options: XMLHashOptions) { | ||
root = XMLElement(name: rootElementName, options: options) | ||
self.options = options | ||
super.init() | ||
} | ||
|
||
let root: XMLElement | ||
var parentStack = Stack<XMLElement>() | ||
let options: XMLHashOptions | ||
var parsingError: ParsingError? | ||
|
||
func parse(_ data: Data) -> XMLIndexer { | ||
// clear any prior runs of parse... expected that this won't be necessary, | ||
// but you never know | ||
parentStack.removeAll() | ||
|
||
parentStack.push(root) | ||
|
||
let parser = XMLParser(data: data) | ||
parser.shouldProcessNamespaces = options.shouldProcessNamespaces | ||
parser.delegate = self | ||
_ = parser.parse() | ||
|
||
if options.detectParsingErrors, let err = parsingError { | ||
return XMLIndexer.parsingError(err) | ||
} else { | ||
return XMLIndexer(root) | ||
} | ||
} | ||
|
||
func parser(_ parser: XMLParser, | ||
didStartElement elementName: String, | ||
namespaceURI: String?, | ||
qualifiedName qName: String?, | ||
attributes attributeDict: [String: String]) { | ||
let currentNode = parentStack | ||
.top() | ||
.addElement(elementName, withAttributes: attributeDict, caseInsensitive: self.options.caseInsensitive) | ||
|
||
parentStack.push(currentNode) | ||
} | ||
|
||
func parser(_ parser: XMLParser, foundCharacters string: String) { | ||
let current = parentStack.top() | ||
|
||
current.addText(string) | ||
} | ||
|
||
func parser(_ parser: XMLParser, | ||
didEndElement elementName: String, | ||
namespaceURI: String?, | ||
qualifiedName qName: String?) { | ||
parentStack.drop() | ||
} | ||
|
||
func parser(_ parser: XMLParser, foundCDATA CDATABlock: Data) { | ||
if let cdataText = String(data: CDATABlock, encoding: String.Encoding.utf8) { | ||
let current = parentStack.top() | ||
|
||
current.addText(cdataText) | ||
} | ||
} | ||
|
||
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) { | ||
#if os(Linux) && !swift(>=4.1.50) | ||
if let err = parseError as? NSError { | ||
parsingError = ParsingError( | ||
line: err.userInfo["NSXMLParserErrorLineNumber"] as? Int ?? 0, | ||
column: err.userInfo["NSXMLParserErrorColumn"] as? Int ?? 0) | ||
} | ||
#else | ||
let err = parseError as NSError | ||
parsingError = ParsingError( | ||
line: err.userInfo["NSXMLParserErrorLineNumber"] as? Int ?? 0, | ||
column: err.userInfo["NSXMLParserErrorColumn"] as? Int ?? 0) | ||
#endif | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// | ||
// IndexOp.swift | ||
// SWXMLHash | ||
// | ||
// Copyright (c) 2022 David Mohundro | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. | ||
// | ||
|
||
import Foundation | ||
|
||
/// Represents an indexed operation against a lazily parsed `XMLIndexer` | ||
public class IndexOp { | ||
var index: Int | ||
let key: String | ||
|
||
init(_ key: String) { | ||
self.key = key | ||
self.index = -1 | ||
} | ||
|
||
func toString() -> String { | ||
if index >= 0 { | ||
return key + " " + index.description | ||
} | ||
|
||
return key | ||
} | ||
} | ||
|
||
/// Represents a collection of `IndexOp` instances. Provides a means of iterating them | ||
/// to find a match in a lazily parsed `XMLIndexer` instance. | ||
public class IndexOps { | ||
var ops: [IndexOp] = [] | ||
|
||
let parser: LazyXMLParser | ||
|
||
init(parser: LazyXMLParser) { | ||
self.parser = parser | ||
} | ||
|
||
func findElements() -> XMLIndexer { | ||
parser.startParsing(ops) | ||
let indexer = XMLIndexer(parser.root) | ||
var childIndex = indexer | ||
for oper in ops { | ||
childIndex = childIndex[oper.key] | ||
if oper.index >= 0 { | ||
childIndex = childIndex[oper.index] | ||
} | ||
} | ||
ops.removeAll(keepingCapacity: false) | ||
return childIndex | ||
} | ||
|
||
func stringify() -> String { | ||
var ret = "" | ||
for oper in ops { | ||
ret += "[" + oper.toString() + "]" | ||
} | ||
return ret | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
// | ||
// LazyXMLParser.swift | ||
// SWXMLHash | ||
// | ||
// Copyright (c) 2022 David Mohundro | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. | ||
// | ||
|
||
import Foundation | ||
|
||
/// The implementation of XMLParserDelegate and where the lazy parsing actually happens. | ||
class LazyXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate { | ||
required init(_ options: XMLHashOptions) { | ||
root = XMLElement(name: rootElementName, options: options) | ||
self.options = options | ||
super.init() | ||
} | ||
|
||
var root: XMLElement | ||
var parentStack = Stack<XMLElement>() | ||
var elementStack = Stack<String>() | ||
|
||
var data: Data? | ||
var ops: [IndexOp] = [] | ||
let options: XMLHashOptions | ||
|
||
func parse(_ data: Data) -> XMLIndexer { | ||
self.data = data | ||
return XMLIndexer(self) | ||
} | ||
|
||
func startParsing(_ ops: [IndexOp]) { | ||
// reset state for a new lazy parsing run | ||
root = XMLElement(name: rootElementName, options: root.options) | ||
parentStack.removeAll() | ||
parentStack.push(root) | ||
|
||
self.ops = ops | ||
let parser = XMLParser(data: data!) | ||
parser.shouldProcessNamespaces = options.shouldProcessNamespaces | ||
parser.delegate = self | ||
_ = parser.parse() | ||
} | ||
|
||
func parser(_ parser: XMLParser, | ||
didStartElement elementName: String, | ||
namespaceURI: String?, | ||
qualifiedName qName: String?, | ||
attributes attributeDict: [String: String]) { | ||
elementStack.push(elementName) | ||
|
||
if !onMatch() { | ||
return | ||
} | ||
|
||
let currentNode = parentStack | ||
.top() | ||
.addElement(elementName, withAttributes: attributeDict, caseInsensitive: self.options.caseInsensitive) | ||
parentStack.push(currentNode) | ||
} | ||
|
||
func parser(_ parser: XMLParser, foundCharacters string: String) { | ||
if !onMatch() { | ||
return | ||
} | ||
|
||
let current = parentStack.top() | ||
|
||
current.addText(string) | ||
} | ||
|
||
func parser(_ parser: XMLParser, foundCDATA CDATABlock: Data) { | ||
if !onMatch() { | ||
return | ||
} | ||
|
||
if let cdataText = String(data: CDATABlock, encoding: String.Encoding.utf8) { | ||
let current = parentStack.top() | ||
|
||
current.addText(cdataText) | ||
} | ||
} | ||
|
||
func parser(_ parser: XMLParser, | ||
didEndElement elementName: String, | ||
namespaceURI: String?, | ||
qualifiedName qName: String?) { | ||
let match = onMatch() | ||
|
||
elementStack.drop() | ||
|
||
if match { | ||
parentStack.drop() | ||
} | ||
} | ||
|
||
func onMatch() -> Bool { | ||
// we typically want to compare against the elementStack to see if it matches ops, *but* | ||
// if we're on the first element, we'll instead compare the other direction. | ||
if elementStack.items.count > ops.count { | ||
return elementStack.items.starts(with: ops.map { $0.key }) | ||
} else { | ||
return ops.map { $0.key }.starts(with: elementStack.items) | ||
} | ||
} | ||
} |
Oops, something went wrong.