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

Conversation

mrotrifork
Copy link
Contributor

This PR covers the following 3 features. Might have been better to split it in 3 pull requests, but let us see if we can go through them one by one and I will make the necessary adjustments:

Feature 1: Ability to get and set text and attributes on single element using the Accessor, and keep the modified elements when creating an xml document. The same small change could also be applied to other types than text/string. It requires that the accessor is not created as a constant.

var accessor = XML.Accessor(singleElement())
accessor.text = "text2"
XCTAssertEqual(accessor.text, "text2", "set text on first single element")

Feature 2: Ability to append a new element to an accessor.

let accessor = XML.Accessor(singleElement())
accessor.append(singleElement())
XCTAssertEqual(accessor["RootElement"].text, "text", "check newly added element")

Feature 3: Global flag for ignoring namespaces when accessing elements. Currently I am using this framework with SOAP XML, which has dynamically prefixed namespaces on all elements.

<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<env:RootElement key=\"value\">
  <ns1:ChildElement>childText1</ns1:ChildElement>
  <ns2:ChildElement>childText2</ns2:ChildElement>
</env:RootElement>
let accessor = XML.Accessor(xmlFromAbove())
XML.ignoreNamespaces = true
XCTAssertEqual(accessor["ChildElement"].first.text, "childText1", "access text for first child element ignoring namespace")

Bugfix 1: XML documents are created with an extra space when no attributes are added for an element.
Bugfix 2: XML declaration prefix is not optional.
Bugfix 3: XML Element does not have a full initialiser.

All 3 features have corresponding unit tests. Let me know what you think.

Global XML setting for ignoring namespaces.
If set to true all accessors will ignore the first part of an element name up to a semicolon (:).
*/
public static var ignoreNamespaces = false
Copy link
Member

@kazuhiro4949 kazuhiro4949 Sep 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ignoreNamespaces should be a local property. Could you place it on Element class?

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 CDATA: Data?
        open var ignoreNamespaces: Bool // <-

        public init(name: String, ignoreNamespaces: Bool = false, text: String? = nil, attributes: [String: String] = [:], childElements: [Element] = []) {

and set the boolean value in Parser class

extension XML {
    class Parser: NSObject, XMLParserDelegate {

        // ...

        init(ignoreNamespaces: Bool = false) {
            self.ignoreNamespaces = ignoreNamespaces
            trimmingManner = nil
            documentRoot = Element(name: "XML.Parser.AbstructedDocumentRoot", ignoreNamespaces: ignoreNamespaces)
        }
        
        init(trimming manner: CharacterSet, ignoreNamespaces: Bool = false) {
            self.ignoreNamespaces = ignoreNamespaces
            trimmingManner = manner
            documentRoot = Element(name: "XML.Parser.AbstructedDocumentRoot", ignoreNamespaces: ignoreNamespaces)
        }
        
        // MARK:- private
        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, ignoreNamespaces: ignoreNamespaces) // <-

change XML I/F.

open class XML {

//    /**
//    Global XML setting for ignoring namespaces.
//    If set to true all accessors will ignore the first part of an element name up to a semicolon (:).
//     */
//    public static var ignoreNamespaces = false

    /**
    Interface to parse NSData
    
    - parameter data:NSData XML document
    - returns:Accessor object to access XML document
    */
    open class func parse(_ data: Data, ignoreNamespaces: Bool = false) -> Accessor {
        return Parser(ignoreNamespaces: ignoreNamespaces).parse(data)
    }
    
    /**
     Interface to parse String
     
     - Parameter str:String XML document
     - Returns:Accessor object to access XML document
     */
    open class func parse(_ str: String, ignoreNamespaces: Bool = false) throws -> Accessor { // <-
        guard let data = str.data(using: String.Encoding.utf8) else {
            throw XMLError.failToEncodeString
        }
        
        return Parser(ignoreNamespaces: ignoreNamespaces).parse(data)
    }
    
    /**
     Interface to parse NSData
     
     - parameter data:NSData XML document
     - parameter manner:NSCharacterSet If you wannna trim Text, assign this arg
     - returns:Accessor object to access XML document
     */
    open class func parse(_ data: Data, trimming manner: CharacterSet, 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
     */
    open class func parse(_ str: String, trimming manner: CharacterSet, ignoreNamespaces: Bool = false) throws -> Accessor { // <-
        guard let data = str.data(using: String.Encoding.utf8) else {
            throw XMLError.failToEncodeString
        }
        
        return Parser(trimming: manner, ignoreNamespaces: ignoreNamespaces).parse(data)
    }

@kazuhiro4949
Copy link
Member

@mrotrifork We appreciate your contribution.
I reviewed this pull-request.
Check the comment :)

@mrotrifork
Copy link
Contributor Author

Good feedback. I moved ignoreNamespaces to live inside XML.Element which can be set via parse(). I also cleaned up some of the initialisers.

@kazuhiro4949 kazuhiro4949 merged commit ad5eeea into yahoojapan:master Sep 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants