diff --git a/README.md b/README.md index 4bdc479..b040259 100755 --- a/README.md +++ b/README.md @@ -226,12 +226,12 @@ entity.names ?<< xml.ResultSet.Result.Hit[1].Name.text // assign if it has text ```swift let numberOfHits = xml.ResultSet.Result.Hit.all?.count ``` -### Check error +### 8. Check error ```swift print(xml.ResultSet.Result.TypoKey) // -> "TypoKey not found." ``` -### Access as SequenceType +### 9. Access as SequenceType + for-in ```swift for element in xml.ResultSet.Result.Hit { @@ -243,6 +243,12 @@ for element in xml.ResultSet.Result.Hit { xml.ResultSet.Result.Hit.map { $0.Name.text } ``` +### 9. Generate XML document +```swift +print(Converter(xml.ResultSet).makeDocument()) +``` + + ## Work with Alamofire SwiftyXMLParser goes well with [Alamofire](https://github.com/Alamofire/Alamofire). You can parse the response easily. diff --git a/SwiftyXMLParser.xcodeproj/project.pbxproj b/SwiftyXMLParser.xcodeproj/project.pbxproj index 2c91846..754ff08 100755 --- a/SwiftyXMLParser.xcodeproj/project.pbxproj +++ b/SwiftyXMLParser.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 2BD943341CB22107007D5FFC /* BrokenXMLDocument.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2BD943331CB22107007D5FFC /* BrokenXMLDocument.xml */; }; 2BD943361CB2210D007D5FFC /* XMLDocument.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2BD943351CB2210D007D5FFC /* XMLDocument.xml */; }; 2BD943381CB23CFE007D5FFC /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BD943371CB23CFE007D5FFC /* Error.swift */; }; + 974D50012243BB0E0015B768 /* ConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 974D50002243BB0E0015B768 /* ConverterTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -54,6 +55,7 @@ 2BD943351CB2210D007D5FFC /* XMLDocument.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = XMLDocument.xml; sourceTree = ""; }; 2BD943371CB23CFE007D5FFC /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; 81B640ED1FC77E6400BE1AB5 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; + 974D50002243BB0E0015B768 /* ConverterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConverterTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -114,6 +116,7 @@ 2BD943321CB220FA007D5FFC /* Fixture */, 2B6D92421C7F0587000D2D06 /* SwiftyXMLParserTests.swift */, 2BD9432A1CB220E0007D5FFC /* XMLTests.swift */, + 974D50002243BB0E0015B768 /* ConverterTests.swift */, 2BD9432C1CB220E5007D5FFC /* ParserTests.swift */, 2BD9432E1CB220EA007D5FFC /* AccessorTests.swift */, 2BD943301CB220F1007D5FFC /* CustomOperatorTests.swift */, @@ -258,6 +261,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 974D50012243BB0E0015B768 /* ConverterTests.swift in Sources */, 2BD943311CB220F1007D5FFC /* CustomOperatorTests.swift in Sources */, 2BD9432F1CB220EA007D5FFC /* AccessorTests.swift in Sources */, 2B6D92431C7F0587000D2D06 /* SwiftyXMLParserTests.swift in Sources */, diff --git a/SwiftyXMLParser/Accessor.swift b/SwiftyXMLParser/Accessor.swift index dede836..a68a686 100755 --- a/SwiftyXMLParser/Accessor.swift +++ b/SwiftyXMLParser/Accessor.swift @@ -449,3 +449,64 @@ extension XML { } } } + +extension XML { + /// Conveter to make xml document from Accessor. + public class Converter { + let accessor: XML.Accessor + + public init(_ accessor: XML.Accessor) { + self.accessor = accessor + } + + /** + If Accessor object has correct XML path, return the XML element, otherwith return error + + example: + + ``` + let xml = try! XML.parse("text") + let elem = xml.doc + + print(Converter(elem).makeDocument()) + // => text + ``` + + */ + public func makeDocument() throws -> String { + if case .failure(let err) = accessor { + throw err + } + + var doc: String = "" + for hit in accessor { + switch hit { + case .singleElement(let element): + doc += traverse(element) + case .sequence(let elements): + doc += elements.reduce("") { (sum, el) in sum + traverse(el) } + case .failure(let error): + throw error + } + } + + return doc + } + + private func traverse(_ element: Element) -> String { + let name = element.name + let text = element.text ?? "" + let attrs = element.attributes.map { (k, v) in "\(k)=\"\(v)\"" }.joined(separator: " ") + + let childDocs = element.childElements.reduce("", { (result, element) in + result + traverse(element) + }) + + if name == "XML.Parser.AbstructedDocumentRoot" { + return childDocs + } else { + return "<\(name) \(attrs)>\(text)\(childDocs)" + } + } + } +} diff --git a/SwiftyXMLParser/XML.swift b/SwiftyXMLParser/XML.swift index 94b2988..884c304 100755 --- a/SwiftyXMLParser/XML.swift +++ b/SwiftyXMLParser/XML.swift @@ -135,4 +135,8 @@ open class XML { return Parser(trimming: manner).parse(data) } + + open class func document(_ accessor: Accessor) throws -> String { + return try Converter(accessor).makeDocument() + } } diff --git a/SwiftyXMLParserTests/ConverterTests.swift b/SwiftyXMLParserTests/ConverterTests.swift new file mode 100644 index 0000000..66bc978 --- /dev/null +++ b/SwiftyXMLParserTests/ConverterTests.swift @@ -0,0 +1,131 @@ +/** + * The MIT License (MIT) + * + * Copyright (C) 2016 Yahoo Japan Corporation. + * + * 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 XCTest +import SwiftyXMLParser + +class ConverterTests: XCTestCase { + func testMakeDosument() { + // no chiled element, text only + do { + let element = XML.Element(name: "name", + text: "text", + attributes: ["key": "value"]) + let converter = XML.Converter(XML.Accessor(element)) + + guard let result = try? converter.makeDocument() else { + XCTFail("fail to make document") + return + } + let extpected = """ + text + """ + XCTAssertEqual(result, extpected) + } + + // no text, chiled elements only + do { + let childElements = [ + XML.Element(name: "c_name1", text: "c_text1", attributes: ["c_key1": "c_value1"]), + XML.Element(name: "c_name2", text: "c_text2", attributes: ["c_key2": "c_value2"]) + ] + let element = XML.Element(name: "name", + text: nil, + attributes: ["key": "value"], + childElements: childElements) + + let converter = XML.Converter(XML.Accessor(element)) + guard let result = try? converter.makeDocument() else { + XCTFail("fail to make document") + return + } + let extpected = """ + c_text1c_text2 + """ + XCTAssertEqual(result, extpected) + } + + // both text and chiled element + do { + let childElements = [ + XML.Element(name: "c_name1", text: "c_text1", attributes: ["c_key1": "c_value1"]), + XML.Element(name: "c_name2", text: "c_text2", attributes: ["c_key2": "c_value2"]) + ] + let element = XML.Element(name: "name", + text: "text", + attributes: ["key": "value"], + childElements: childElements) + let converter = XML.Converter(XML.Accessor(element)) + guard let result = try? converter.makeDocument() else { + XCTFail("fail to make document") + return + } + let extpected = """ + textc_text1c_text2 + """ + XCTAssertEqual(result, extpected) + } + + // nested child elements + do { + let grateGrandchildElements = [ + XML.Element(name: "ggc_name1", text: "ggc_text1", attributes: ["ggc_key1": "ggc_value1"]) + ] + + let grandchildElements = [ + XML.Element(name: "gc_name1", text: "gc_text1", attributes: ["gc_key1": "gc_value1"], childElements: grateGrandchildElements) + ] + + let childElements = [ + XML.Element(name: "c_name1", text: "c_text1", attributes: ["c_key1": "c_value1"]), + XML.Element(name: "c_name2", text: "c_text2", attributes: ["c_key2": "c_value2"], childElements: grandchildElements) + ] + let element = XML.Element(name: "name", + text: "text", + attributes: ["key": "value"], + childElements: childElements) + let converter = XML.Converter(XML.Accessor(element)) + guard let result = try? converter.makeDocument() else { + XCTFail("fail to make document") + return + } + let extpected = """ + textc_text1c_text2gc_text1ggc_text1 + """ + XCTAssertEqual(result, extpected) + } + } +} + +extension XML.Element { + convenience init(name: String, + text: String? = nil, + attributes: [String: String] = [String: String](), + childElements: [XML.Element] = [XML.Element]()) { + self.init(name: name) + self.text = text + self.attributes = attributes + self.childElements = childElements + } +}