Skip to content

Commit

Permalink
Merge pull request #64 from mrotrifork/feature/edit-xml
Browse files Browse the repository at this point in the history
Feature: Set text, append element and ignore namespaces
  • Loading branch information
kazuhiro4949 authored Sep 20, 2021
2 parents a188a1c + 109912c commit ad5eeea
Show file tree
Hide file tree
Showing 9 changed files with 331 additions and 126 deletions.
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

0 comments on commit ad5eeea

Please sign in to comment.