Skip to content
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

Feature: Set text, append element and ignore namespaces #64

Merged
merged 13 commits into from
Sep 20, 2021
Merged
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,7 @@ DerivedData/
!default.perspectivev3



SwiftyXMLParser.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
104 changes: 64 additions & 40 deletions SwiftyXMLParser/Accessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,23 +128,26 @@ extension XML {
let accessor: Accessor
switch self {
case .singleElement(let element):
let filterdElements = element.childElements.filter { $0.name == key }
if filterdElements.isEmpty {
let childElements = element.childElements.filter {
if $0.ignoreNamespaces {
return key == $0.name.components(separatedBy: ":").last ?? $0.name
} else {
return key == $0.name
}
}
if childElements.isEmpty {
let error = accessError("\(key) not found.")
accessor = Accessor(error)
} else if filterdElements.count == 1 {
accessor = Accessor(filterdElements[0])
accessor = Accessor(error)
} else if childElements.count == 1 {
accessor = Accessor(childElements[0])
} else {
accessor = Accessor(filterdElements)
accessor = Accessor(childElements)
}
case .failure(let error):
accessor = Accessor(error)
case .sequence(_):
fallthrough
accessor = Accessor(error)
default:
let error = accessError("cannot access \(key), because of multiple elements")
accessor = Accessor(error)
break
accessor = Accessor(error)
}
return accessor
}
Expand Down Expand Up @@ -235,22 +238,27 @@ extension XML {
}
return name
}


/// get and set text on single element
public var text: String? {
let text: String?
switch self {
case .singleElement(let element):
text = element.text
case .failure(_), .sequence(_):
fallthrough
default:
text = nil
break
get {
switch self {
case .singleElement(let element):
return element.text
default:
return nil
}
}
set {
switch self {
case .singleElement(let element):
element.text = newValue
default:
break
}
}
return text
}


/// syntax sugar to access Bool Text
public var bool: Bool? {
return text.flatMap { $0 == "true" }
Expand All @@ -272,19 +280,24 @@ extension XML {
return text.flatMap({Double($0)})
}

/// access to XML Attributes
/// get and set XML attributes on single element
public var attributes: [String: String] {
let attributes: [String: String]
switch self {
case .singleElement(let element):
attributes = element.attributes
case .failure(_), .sequence(_):
fallthrough
default:
attributes = [String: String]()
break
get {
switch self {
case .singleElement(let element):
return element.attributes
default:
return [String: String]()
}
}
set {
switch self {
case .singleElement(let element):
element.attributes = newValue
default:
break
}
}
return attributes
}

/// access to child Elements
Expand Down Expand Up @@ -396,6 +409,15 @@ extension XML {
}
}

public func append(_ newElement: Element) {
switch self {
case .singleElement(let element):
element.childElements.append(newElement)
default:
break
}
}

// MARK: - SequenceType

public func makeIterator() -> AnyIterator<Accessor> {
Expand Down Expand Up @@ -451,7 +473,7 @@ extension XML {
}

extension XML {
/// Conveter to make xml document from Accessor.
/// Converter to make xml document from Accessor.
public class Converter {
let accessor: XML.Accessor

Expand All @@ -460,7 +482,9 @@ extension XML {
}

/**
If Accessor object has correct XML path, return the XML element, otherwith return error
Convert accessor back to XML document string.

- Parameter withDeclaration:Prefix with standard XML declaration (default true)

example:

Expand All @@ -473,12 +497,12 @@ extension XML {
```

*/
public func makeDocument() throws -> String {
public func makeDocument(withDeclaration: Bool = true) throws -> String {
if case .failure(let err) = accessor {
throw err
}

var doc: String = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
var doc = withDeclaration ? "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" : ""
for hit in accessor {
switch hit {
case .singleElement(let element):
Expand All @@ -496,7 +520,7 @@ extension XML {
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 attrs = element.attributes.map { (k, v) in "\(k)=\"\(v)\"" }.joined(separator: " ")

let childDocs = element.childElements.reduce("", { (result, element) in
result + traverse(element)
Expand All @@ -505,7 +529,7 @@ extension XML {
if name == "XML.Parser.AbstructedDocumentRoot" {
return childDocs
} else {
return "<\(name) \(attrs)>\(text)\(childDocs)</\(name)>"
return "<\(name)\(attrs.isEmpty ? "" : " ")\(attrs)>\(text)\(childDocs)</\(name)>"
}
}
}
Expand Down
29 changes: 23 additions & 6 deletions SwiftyXMLParser/Element.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,34 @@ extension XML {
open class Element {
open var name: String
open var text: String?
open var attributes = [String: String]()
open var childElements = [Element]()
open var lineNumberStart = -1
open var lineNumberEnd = -1
open var attributes: [String: String]
open var childElements: [Element]
open var lineNumberStart: Int
open var lineNumberEnd: Int
open var CDATA: Data?
open var ignoreNamespaces: Bool

// for println
open weak var parentElement: Element?

public init(name: String) {

public init(
name: String,
text: String? = nil,
attributes: [String: String] = [:],
childElements: [Element] = [],
lineNumberStart: Int = -1,
lineNumberEnd: Int = -1,
CDATA: Data? = nil,
ignoreNamespaces: Bool = false
) {
self.name = name
self.text = text
self.attributes = attributes
self.childElements = childElements
self.lineNumberStart = lineNumberStart
self.lineNumberEnd = lineNumberEnd
self.CDATA = CDATA
self.ignoreNamespaces = ignoreNamespaces
}
}
}
19 changes: 9 additions & 10 deletions SwiftyXMLParser/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ extension XML {
/// So the result of parsing is missing.
/// See https://developer.apple.com/documentation/foundation/xmlparser/errorcode
private(set) var error: XMLError?

func parse(_ data: Data) -> Accessor {
stack = [Element]()
stack.append(documentRoot)
Expand All @@ -46,22 +46,21 @@ extension XML {
return Accessor(documentRoot)
}
}

override init() {
trimmingManner = nil
}

init(trimming manner: CharacterSet) {
trimmingManner = manner

init(trimming manner: CharacterSet? = nil, ignoreNamespaces: Bool = false) {
self.trimmingManner = manner
self.ignoreNamespaces = ignoreNamespaces
self.documentRoot = Element(name: "XML.Parser.AbstructedDocumentRoot", ignoreNamespaces: ignoreNamespaces)
}

// MARK:- private
fileprivate var documentRoot = Element(name: "XML.Parser.AbstructedDocumentRoot")
fileprivate var documentRoot: Element
fileprivate var stack = [Element]()
fileprivate let trimmingManner: CharacterSet?
fileprivate let ignoreNamespaces: Bool

func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
let node = Element(name: elementName)
let node = Element(name: elementName, ignoreNamespaces: ignoreNamespaces)
node.lineNumberStart = parser.lineNumber
if !attributeDict.isEmpty {
node.attributes = attributeDict
Expand Down
60 changes: 23 additions & 37 deletions SwiftyXMLParser/XML.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,57 +86,43 @@ public func ?<< <T>(lhs: inout [T], rhs: T?) {
```
*/
open class XML {

/**
Interface to parse NSData

- parameter data:NSData XML document
- returns:Accessor object to access XML document
*/
open class func parse(_ data: Data) -> Accessor {
return Parser().parse(data)
}

/**
Interface to parse String

- Parameter str:String XML document
- Returns:Accessor object to access XML document
*/
open class func parse(_ str: String) throws -> Accessor {
guard let data = str.data(using: String.Encoding.utf8) else {
throw XMLError.failToEncodeString
}

return Parser().parse(data)
}

/**
Interface to parse NSData
Interface to parse Data

- parameter data:NSData XML document
- parameter manner:NSCharacterSet If you wannna trim Text, assign this arg
- parameter data:Data XML document
- parameter manner:CharacterSet If you want to trim text (default off)
- parameter ignoreNamespaces:Bool If set to true all accessors will ignore the first part of an element name up to a semicolon (default false)
- returns:Accessor object to access XML document
*/
open class func parse(_ data: Data, trimming manner: CharacterSet) -> Accessor {
return Parser(trimming: manner).parse(data)
open class func parse(_ data: Data, trimming manner: CharacterSet? = nil, ignoreNamespaces: Bool = false) -> Accessor {
return Parser(trimming: manner, ignoreNamespaces: ignoreNamespaces).parse(data)
}

/**
Interface to parse String

- Parameter str:String XML document
- parameter manner:NSCharacterSet If you wannna trim Text, assign this arg
- Returns:Accessor object to access XML document
- parameter str:String XML document
- parameter manner:CharacterSet If you want to trim text (default off)
- parameter ignoreNamespaces:Bool If set to true all accessors will ignore the first part of an element name up to a semicolon (default false)
- returns:Accessor object to access XML document
*/
open class func parse(_ str: String, trimming manner: CharacterSet) throws -> Accessor {
open class func parse(_ str: String, trimming manner: CharacterSet? = nil, ignoreNamespaces: Bool = false) throws -> Accessor {
guard let data = str.data(using: String.Encoding.utf8) else {
throw XMLError.failToEncodeString
}

return Parser(trimming: manner).parse(data)
return Parser(trimming: manner, ignoreNamespaces: ignoreNamespaces).parse(data)
}

open class func document(_ accessor: Accessor) throws -> String {
return try Converter(accessor).makeDocument()

/**
Convert accessor back to XML document string.

- parameter accessor:XML accessor
- parameter withDeclaration:Prefix with standard XML declaration (default true)
- returns:XML document string
*/
open class func document(_ accessor: Accessor, withDeclaration: Bool = true) throws -> String {
return try Converter(accessor).makeDocument(withDeclaration: withDeclaration)
}
}
Loading