SWXMLHash Documentation

repository·main·Indexed 23 days ago

https://github.com/drmohundro/swxmlhash

A Swift library that provides a high-level wrapper around XMLParser to translate XML into a dictionary-like structure (hash). It supports subscript syntax for element lookups, attribute access, lazy processing for large files, and custom object deserialization via the XMLObjectDeserialization and XMLValueDeserialization protocols.

Tokens
2.4K
Snippets
9
Records
17
Agent score
31%

What's inside SWXMLHash

  1. What is SWXMLHash and how does it work?

    main
    SWXMLHash is a wrapper around Swift's XMLParser (formerly NSXMLParser). It provides a conceptual translation from XML structures into a dictionary of arrays (a hash), making XML data easier to navigate in a way similar to SwiftyJSON.
  2. Handle XML parsing and indexing errors

    main

    Errors can be handled using standard Swift do-catch blocks with IndexingError, or by switching on the result of an indexer lookup.

    Note: Error handling via do-catch or switching on the result will not work with lazy loaded XML, because parsing only occurs when .element or .all is called, making it impossible to know if an element exists prior to that call.

  3. Install SWXMLHash via CocoaPods

    main

    To install using CocoaPods, add pod 'SWXMLHash', '~> 7.0.0' to your Podfile within your target block. Ensure use_frameworks! is enabled. Run pod install to complete the installation.

    platform :ios, '10.0'
    use_frameworks!
    
    target 'YOUR_TARGET_NAME' do
      pod 'SWXMLHash', '~> 7.0.0'
    end
  4. Install SWXMLHash via Swift Package Manager

    main

    To add SWXMLHash as a dependency in your Swift project, update the dependencies array in your Package.swift file. Use a version reference starting from 7.0.0 to ensure compatibility.

    dependencies: [
        .package(url: "https://github.com/drmohundro/SWXMLHash.git", from: "7.0.0")
    ]
  5. Install SWXMLHash manually

    main
    You can install SWXMLHash manually by cloning the repository. It is recommended to use git submodules to track specific commits. Once cloned, you can either drop the relevant .swift files into your project or include the SWXMLHash.xcodeproj if you are using a workspace.
  6. Deserialize XML into custom objects

    main

    To map XML to custom Swift types, implement the XMLObjectDeserialization protocol. This allows you to use .value() to convert an XML node directly into an array or a single instance of your custom type.

    Built-in converters support Int, Double, Float, Bool, and String (including optional variants) for leaf nodes and attributes.

    struct Book: XMLObjectDeserialization {
        let title: String
        let price: Double
        let year: Int
        let amount: Int?
        let isbn: Int
        let category: [String]
    
        static func deserialize(_ node: XMLIndexer) throws -> Book {
            return try Book(
                title: node["title"].value(),
                price: node["price"].value(),
                year: node["year"].value(),
                amount: node["amount"].value(),
                isbn: node.value(ofAttribute: "isbn"),
                category : node["categories"]["category"].value()
            )
        }
    }
    
    // Usage
    let books: [Book] = try xml["root"]["books"]["book"].value()
  7. Resolve 'Ambiguous reference to member 'subscript'' when calling .value()

    main

    This error occurs during deserialization when the type on the left-hand side of the expression does not implement XMLObjectDeserialization (for groups) or XMLElementDeserializable (for single elements).

    For example, calling .value() to assign to a Date will fail because Date does not have a built-in deserializer in SWXMLHash. To fix this, you must implement the XMLElementDeserializable protocol for your custom type.

  8. Access XML attributes

    main

    Attributes can be accessed in two ways:

    1. By calling .element?.attribute(by: "name") on an element.
    2. By using the .withAttribute("name", "value") method to filter for elements that possess a specific attribute value.
  9. Filter XML elements

    main

    You can filter elements using .filterAll (to filter based on the element itself) and .filterChildren (to filter based on the index of the child).

    let subIndexer = xml!["root"]["catalog"]["book"]
        .filterAll { elem, _ in elem.attribute(by: "id")!.text == "bk102" }
        .filterChildren { _, index in index >= 1 && index <= 3 }
    
    print(subIndexer.children[0].element?.text)
  10. Implement custom value conversion with XMLValueDeserialization

    main

    If you need to convert a string value from XML into a custom type (like Date), implement the XMLValueDeserialization protocol. You must provide implementations for deserialize(_ element: XMLHash.XMLElement) and deserialize(_ attribute: XMLAttribute).

    extension Date: XMLValueDeserialization {
        public static func deserialize(_ element: XMLHash.XMLElement) throws -> Date {
            let date = stringToDate(element.text)
            guard let validDate = date else {
                throw XMLDeserializationError.typeConversionFailed(type: "Date", element: element)
            }
            return validDate
        }
    
        public static func deserialize(_ attribute: XMLAttribute) throws -> Date {
            let date = stringToDate(attribute.text)
            guard let validDate = date else {
                throw XMLDeserializationError.attributeDeserializationFailed(type: "Date", attribute: attribute)
            }
            return validDate
        }
    
        public func validate() throws {}
    
        private static func stringToDate(_ dateAsString: String) -> Date? {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "EEEE, dd MMMM yyyy HH:mm:ss SSS"
            return dateFormatter.date(from: dateAsString)
        }
    }
    
    // Usage
    let dt: Date = try xml["root"]["elem"].value()
  11. Iterate over elements using all and children

    main

    Use .all to iterate over all nodes at the current indexed level. Use .children to iterate over the immediate child elements of an indexer.

    // Iterate over all elements at current level
    for elem in xml["root"]["catalog"]["book"].all {
        print(elem["genre"].element!.text!)
    }
    
    // Recursive enumeration of all children
    func enumerate(indexer: XMLIndexer) {
        for child in indexer.children {
            print(child.element!.name)
            enumerate(child)
        }
    }