Giter Site home page Giter Site logo

coreoffice / xmlcoder Goto Github PK

View Code? Open in Web Editor NEW
788.0 22.0 104.0 1.65 MB

Easy XML parsing using Codable protocols in Swift

Home Page: https://coreoffice.github.io/XMLCoder/

License: MIT License

Swift 99.69% Ruby 0.17% Shell 0.14%
xml xml-parser swift swift4 codable carthage cocoapods encoder decoder xml-serializer

xmlcoder's Introduction

XMLCoder

Encoder & Decoder for XML using Swift's Codable protocols.

Version License Platform Coverage

This package is a fork of the original ShawnMoore/XMLParsing with more features and improved test coverage. Automatically generated documentation is available on our GitHub Pages.

Join our Discord for any questions and friendly banter.

Example

import XMLCoder
import Foundation

let sourceXML = """
<note>
    <to>Bob</to>
    <from>Jane</from>
    <heading>Reminder</heading>
    <body>Don't forget to use XMLCoder!</body>
</note>
"""

struct Note: Codable {
    let to: String
    let from: String
    let heading: String
    let body: String
}

let note = try! XMLDecoder().decode(Note.self, from: Data(sourceXML.utf8))

let encodedXML = try! XMLEncoder().encode(note, withRootKey: "note")

Advanced features

The following features are available in 0.4.0 release or later (unless stated otherwise):

Stripping namespace prefix

Sometimes you need to handle an XML namespace prefix, like in the XML below:

<h:table xmlns:h="http://www.w3.org/TR/html4/">
  <h:tr>
    <h:td>Apples</h:td>
    <h:td>Bananas</h:td>
  </h:tr>
</h:table>

Stripping the prefix from element names is enabled with shouldProcessNamespaces property:

struct Table: Codable, Equatable {
    struct TR: Codable, Equatable {
        let td: [String]
    }

    let tr: [TR]
}


let decoder = XMLDecoder()

// Setting this property to `true` for the namespace prefix to be stripped
// during decoding so that key names could match.
decoder.shouldProcessNamespaces = true

let decoded = try decoder.decode(Table.self, from: xmlData)

Dynamic node coding

XMLCoder provides two helper protocols that allow you to customize whether nodes are encoded and decoded as attributes or elements: DynamicNodeEncoding and DynamicNodeDecoding.

The declarations of the protocols are very simple:

protocol DynamicNodeEncoding: Encodable {
    static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding
}

protocol DynamicNodeDecoding: Decodable {
    static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding
}

The values returned by corresponding static functions look like this:

enum NodeDecoding {
    // decodes a value from an attribute
    case attribute

    // decodes a value from an element
    case element

    // the default, attempts to decode as an element first,
    // otherwise reads from an attribute
    case elementOrAttribute
}

enum NodeEncoding {
    // encodes a value in an attribute
    case attribute

    // the default, encodes a value in an element
    case element

    // encodes a value in both attribute and element
    case both
}

Add conformance to an appropriate protocol for types you'd like to customize. Accordingly, this example code:

struct Book: Codable, Equatable, DynamicNodeEncoding {
    let id: UInt
    let title: String
    let categories: [Category]

    enum CodingKeys: String, CodingKey {
        case id
        case title
        case categories = "category"
    }

    static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
        switch key {
        case Book.CodingKeys.id: return .both
        default: return .element
        }
    }
}

works for this XML:

<book id="123">
    <id>123</id>
    <title>Cat in the Hat</title>
    <category>Kids</category>
    <category>Wildlife</category>
</book>

Please refer to PR #70 by @JoeMatt for more details.

Coding key value intrinsic

Suppose that you need to decode an XML that looks similar to this:

<?xml version="1.0" encoding="UTF-8"?>
<foo id="123">456</foo>

By default you'd be able to decode foo as an element, but then it's not possible to decode the id attribute. XMLCoder handles certain CodingKey values in a special way to allow proper coding for this XML. Just add a coding key with stringValue that equals "" (empty string). What follows is an example type declaration that encodes the XML above, but special handling of coding keys with those values works for both encoding and decoding.

struct Foo: Codable, DynamicNodeEncoding {
    let id: String
    let value: String

    enum CodingKeys: String, CodingKey {
        case id
        case value = ""
    }

    static func nodeEncoding(forKey key: CodingKey)
    -> XMLEncoder.NodeEncoding {
        switch key {
        case CodingKeys.id:
            return .attribute
        default:
            return .element
        }
    }
}

Thanks to @JoeMatt for implementing this in in PR #73.

Preserving whitespaces in element content

By default whitespaces are trimmed in element content during decoding. This includes string values decoded with value intrinsic keys. Starting with version 0.5 you can now set a property trimValueWhitespaces to false (the default value is true) on XMLDecoder instance to preserve all whitespaces in decoded strings.

Remove whitespace elements

When decoding pretty-printed XML while trimValueWhitespaces is set to false, it's possible for whitespace elements to be added as child elements on an instance of XMLCoderElement. These whitespace elements make it impossible to decode data structures that require custom Decodable logic. Starting with version 0.13.0 you can set a property removeWhitespaceElements to true (the default value is false) on XMLDecoder to remove these whitespace elements.

Choice element coding

Starting with version 0.8, you can encode and decode enums with associated values by conforming your CodingKey type additionally to XMLChoiceCodingKey. This allows encoding and decoding XML elements similar in structure to this example:

<container>
    <int>1</int>
    <string>two</string>
    <string>three</string>
    <int>4</int>
    <int>5</int>
</container>

To decode these elements you can use this type:

enum IntOrString: Codable {
    case int(Int)
    case string(String)
    
    enum CodingKeys: String, XMLChoiceCodingKey {
        case int
        case string
    }
    
    enum IntCodingKeys: String, CodingKey { case _0 = "" }
    enum StringCodingKeys: String, CodingKey { case _0 = "" }
}

This is described in more details in PR #119 by @jsbean and @bwetherfield.

Choice elements with (inlined) complex associated values

Lets extend previous example replacing simple types with complex in assosiated values. This example would cover XML like:

<container>
    <nested attr="n1_a1">
        <val>n1_v1</val>
        <labeled>
            <val>n2_val</val>
        </labeled>
    </nested>
    <simple attr="n1_a1">
        <val>n1_v1</val>
    </simple>
</container>
enum InlineChoice: Equatable, Codable {
    case simple(Nested1)
    case nested(Nested1, labeled: Nested2)
    
    enum CodingKeys: String, CodingKey, XMLChoiceCodingKey {
        case simple, nested
    }
    
    enum SimpleCodingKeys: String, CodingKey { case _0 = "" }
    
    enum NestedCodingKeys: String, CodingKey {
        case _0 = ""
        case labeled
    }
    
    struct Nested1: Equatable, Codable, DynamicNodeEncoding {
        var attr = "n1_a1"
        var val = "n1_v1"
        
        public static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
            switch key {
            case CodingKeys.attr: return .attribute
            default: return .element
            }
        }
    }

    struct Nested2: Equatable, Codable {
        var val = "n2_val"
    }
}

Integrating with Combine

Starting with XMLCoder version 0.9, when Apple's Combine framework is available, XMLDecoder conforms to the TopLevelDecoder protocol, which allows it to be used with the decode(type:decoder:) operator:

import Combine
import Foundation
import XMLCoder

func fetchBook(from url: URL) -> AnyPublisher<Book, Error> {
    return URLSession.shared.dataTaskPublisher(for: url)
        .map(\.data)
        .decode(type: Book.self, decoder: XMLDecoder())
        .eraseToAnyPublisher()
}

This was implemented in PR #132 by @sharplet.

Additionally, starting with XMLCoder 0.11 XMLEncoder conforms to the TopLevelEncoder protocol:

import Combine
import XMLCoder

func encode(book: Book) -> AnyPublisher<Data, Error> {
    return Just(book)
        .encode(encoder: XMLEncoder())
        .eraseToAnyPublisher()
}

The resulting XML in the example above will start with <book, to customize capitalization of the root element (e.g. <Book) you'll need to set an appropriate keyEncoding strategy on the encoder. To change the element name altogether you'll have to change the name of the type, which is an unfortunate limitation of the TopLevelEncoder API.

Root element attributes

Sometimes you need to set attributes on the root element, which aren't directly related to your model type. Starting with XMLCoder 0.11 the encode function on XMLEncoder accepts a new rootAttributes argument to help with this:

struct Policy: Encodable {
    var name: String
}

let encoder = XMLEncoder()
let data = try encoder.encode(Policy(name: "test"), rootAttributes: [
    "xmlns": "http://www.nrf-arts.org/IXRetail/namespace",
    "xmlns:xsd": "http://www.w3.org/2001/XMLSchema",
    "xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
])

The resulting XML will look like this:

<policy xmlns="http://www.nrf-arts.org/IXRetail/namespace"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <name>test</name>
</policy>

This was implemented in PR #160 by @portellaa.

Property wrappers

If your version of Swift allows property wrappers to be used, you may prefer this API to the more verbose dynamic node coding.

For example, this type

struct Book: Codable {
    @Element var id: Int
}

will encode value Book(id: 42) as <Book><id>42</id></Book>. And vice versa, it will decode the latter into the former.

Similarly,

struct Book: Codable {
    @Attribute var id: Int
}

will encode value Book(id: 42) as <Book id="42"></Book> and vice versa for decoding.

If you don't know upfront if a property will be present as an element or an attribute during decoding, use @ElementAndAttribute:

struct Book: Codable {
    @ElementAndAttribute var id: Int
}

This will encode value Book(id: 42) as <Book id="42"><id>42</id></Book>. It will decode both <Book><id>42</id></Book> and <Book id="42"></Book> as Book(id: 42).

This feature is available starting with XMLCoder 0.13.0 and was implemented by @bwetherfield.

XML Headers

You can add an XML header and/or doctype when encoding an object by supplying it to the encode function. These arguments are both optional, and will only render when explicitly provided.

struct User: Codable {
    @Element var username: String
}

let data = try encoder.encode(
    User(username: "Joanis"),
    withRootKey: "user",
    header: XMLHeader(version: 1.0, encoding: "UTF-8"),
    doctype: .system(
        rootElement: "user",
        dtdLocation: "http://example.com/myUser_v1.dtd"
    )
)

Installation

Requirements

Apple Platforms

  • Xcode 11.0 or later
    • IMPORTANT: compiling XMLCoder with Xcode 11.2.0 (11B52) and 11.2.1 (11B500) is not recommended due to crashes with EXC_BAD_ACCESS caused by a compiler bug, please use Xcode 11.3 or later instead. Please refer to #150 for more details.
  • Swift 5.1 or later
  • iOS 9.0 / watchOS 2.0 / tvOS 9.0 / macOS 10.10 or later deployment targets

Linux

  • Ubuntu 18.04 or later
  • Swift 5.1 or later

Windows

  • Swift 5.5 or later.

Swift Package Manager

Swift Package Manager is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies on all platforms.

Once you have your Swift package set up, adding XMLCoder as a dependency is as easy as adding it to the dependencies value of your Package.swift.

dependencies: [
    .package(url: "https://github.com/CoreOffice/XMLCoder.git", from: "0.15.0")
]

If you're using XMLCoder in an app built with Xcode, you can also add it as a direct dependency using Xcode's GUI.

CocoaPods

CocoaPods is a dependency manager for Swift and Objective-C Cocoa projects for Apple's platfoms. You can install it with the following command:

$ gem install cocoapods

Navigate to the project directory and create Podfile with the following command:

$ pod install

Inside of your Podfile, specify the XMLCoder pod:

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'YourApp' do
  # Comment the next line if you're not using Swift or don't want
  # to use dynamic frameworks
  use_frameworks!

  # Pods for YourApp
  pod 'XMLCoder', '~> 0.14.0'
end

Then, run the following command:

$ pod install

Open the the YourApp.xcworkspace file that was created. This should be the file you use everyday to create your app, instead of the YourApp.xcodeproj file.

Carthage

Carthage is a dependency manager for Apple's platfoms that builds your dependencies and provides you with binary frameworks.

Carthage can be installed with Homebrew using the following command:

$ brew update
$ brew install carthage

Inside of your Cartfile, add GitHub path to XMLCoder:

github "CoreOffice/XMLCoder" ~> 0.15.0

Then, run the following command to build the framework:

$ carthage update

Drag the built framework into your Xcode project.

Usage with Vapor

extension XMLEncoder: ContentEncoder {
    public func encode<E: Encodable>(
        _ encodable: E,
        to body: inout ByteBuffer,
        headers: inout HTTPHeaders
    ) throws {
        headers.contentType = .xml
        
        // Note: You can provide an XMLHeader or DocType if necessary
        let data = try self.encode(encodable)
        body.writeData(data)
    }
}

extension XMLDecoder: ContentDecoder {
    public func decode<D: Decodable>(
        _ decodable: D.Type,
        from body: ByteBuffer,
        headers: HTTPHeaders
    ) throws -> D {
        // Force wrap is acceptable, as we're guaranteed these bytes exist through `readableBytes`
        let body = body.readData(length: body.readableBytes)!
        return try self.decode(D.self, from: body)
    }
}

Contributing

This project adheres to the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to [email protected].

Coding Style

This project uses SwiftFormat and SwiftLint to enforce formatting and coding style. We encourage you to run SwiftFormat within a local clone of the repository in whatever way works best for you either manually or automatically via an Xcode extension, build phase or git pre-commit hook etc.

To guarantee that these tools run before you commit your changes on macOS, you're encouraged to run this once to set up the pre-commit hook:

brew bundle # installs SwiftLint, SwiftFormat and pre-commit
pre-commit install # installs pre-commit hook to run checks before you commit

Refer to the pre-commit documentation page for more details and installation instructions for other platforms.

SwiftFormat and SwiftLint also run on CI for every PR and thus a CI build can fail with incosistent formatting or style. We require CI builds to pass for all PRs before merging.

Test Coverage

Our goal is to keep XMLCoder stable and to serialize any XML correctly according to XML 1.0 standard. All of this can be easily tested automatically and we're slowly improving test coverage of XMLCoder and don't expect it to decrease. PRs that decrease the test coverage have a much lower chance of being merged. If you add any new features, please make sure to add tests, likewise for changes and any refactoring in existing code.

xmlcoder's People

Contributors

acecilia avatar alkenso avatar bwetherfield avatar dingwilson avatar drewag avatar evandcoleman avatar flowbe avatar hodovani avatar huwr avatar jayhickey avatar joannis avatar joematt avatar johankool avatar jsbean avatar kikeenrique avatar kkebo avatar lucianopalmeida avatar lutzifer avatar maciejtrybilo avatar martinp7r avatar maxdesiatov avatar nighthawk avatar portellaa avatar qmoya avatar regexident avatar salavert avatar sharplet avatar shawnmoore avatar ultramiraculous avatar wooj2 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

xmlcoder's Issues

Array Support

I'm loving XMLCoder but having a bit of difficulty understanding how to decode an array of elements. Here is an example XML

<Response>
    <Room>1001</Room>
    <Children>
        <Child>
            <Name>James</Name>
            <Age>10</Age>
        </Child>
        <Child>
            <Name>Pete</Name>
            <Age>10</Age>
        </Child>
    </Children>
</Response>

Here is how I'd expect it to be structured in Swift.

struct Response: Codable {
    let Room: Int
    let Children: [Child]
}

struct Child: Codable {
    let Name: String
    let Age: Int
}

Here is how I'm decoding it:

        let decoder = XMLDecoder()
        let response = try! decoder.decode(Response.self, from: xml)

The error printed is:

Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "Name", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "Children", intValue: nil), XMLKey(stringValue: "0", intValue: 0), XMLKey(stringValue: "0", intValue: 0), CodingKeys(stringValue: "Name", intValue: nil)], debugDescription: "No attribute or element found for key CodingKeys(stringValue: \"Name\", intValue: nil) (\"Name\").", underlyingError: nil))

Is this supported functionality of XMLCoder? I couldn't find any docs.

Add SwiftLint to CI runs

We already have a SwiftFormat config in the repository, but there are a lot of PR comments that I feel could be caught by SwiftLint. Besides, we don't run SwiftFormat on CI. We need to get a consistent coding style established and automatically enforced, which would save time during code reviews.

Implement tests for class hierarchy coding

_XMLReferencingEncoder is a class that we potentially would like to refactor or completely get rid of. It's currently excluded from test coverage and it's only used as a return value of superEncoder, which is called when encoding a class hierarchy with base and derived classes. This means we should have a test case that encodes (and decodes for symmetry) a class hiearchy, at least 2 levels, but I think 3 would be great too to cover potential edge cases, with a few codable properties each.

Investigate XML decoding in RJI test case

Currently RJI RSS test case is able to decode the cleaned up XML successfully (multiple "author" elements were removed to be investigated later). The main problem is that when RJI value is encoded it can't be decoded back again without errors, there were attempts to add this to the test with this commented code.

Spaces removed when decoding strings with escaped elements

I am getting this kind of XML from an ancient SOAP server:

<aResponse>&lt;uesb2b:response xmlns:uesb2b=&quot;http://services.b2b.ues.ut.uhg.com/types/plans/&quot; xmlns=&quot;http://services.b2b.ues.ut.uhg.com/types/plans/&quot;&gt;&#xd;
  &lt;uesb2b:st cd=&quot;GA&quot; /&gt;&#xd;
  &lt;uesb2b:obligId val=&quot;01&quot; /&gt;&#xd;
  &lt;uesb2b:shrArrangementId val=&quot;00&quot; /&gt;&#xd;
  &lt;uesb2b:busInsType val=&quot;CG&quot; /&gt;&#xd;
  &lt;uesb2b:metalPlans typ=&quot;Array&quot;
…

When I decode with a struct like:

struct Response: Decodable {
     let aResponse: String
}

Using:

let decoder = XMLDecoder()
decoder.shouldProcessNamespaces = true
let rsp = try decoder.decode(Response.self, from: data)
print(rsp.aResponse)

I get:

<uesb2b:response xmlns:uesb2b="http://services.b2b.ues.ut.uhg.com/types/plans/"xmlns="http://services.b2b.ues.ut.uhg.com/types/plans/">
<uesb2b:st cd="GA"/>
<uesb2b:obligId val="01"/>
<uesb2b:shrArrangementId val="00"/>
<uesb2b:busInsType val="CG"/>
<uesb2b:metalPlans typ="Array"arrayTyp="metalPlan[110]">
<uesb2b:metalPlan cd="AUWJ"rx="286A"level="S"min="0"max="0"/>
<uesb2b:metalPlan cd="AUWK"rx="286A"level="G"min="0"max="0"/><uesb2b:metalPlan …

(Newlines added for legibility). You can see the spaces on either side of the &quot;s in the attribute heavy nodes are removed (last two lines).

This makes the inner XML invalid.

Happy to fix the bug, just point me at the right code, thanks.

Return Unrecognized Elements as a String?

I am trying to implement a parser for a XML format that can contain extensions. These extensions are valid XML, but we don't know what format they will be in.

I'd like to return any unrecognized elements as a string for later parsing by consumers. Is this possible?

An example:

<Data version="2.0">
  <Sample>Testing</Sample>
  <Extensions>
   // This could be anything
  </Extensions>
</Data>

I'd like the following structure:

struct Data: Codable {
  let version: String
  let extensions: String?
}

It seems that since Extensions is valid XML, it always gets parsed and thus I am unable to convert this back to a string.

I was hoping to be able to do this via an init(from: decoder: Decoder) but at that point it appears it is already to late.

Any ideas?

TCX file need an XML root node

TCX file need a root XML node at the start (Strava rejects the file when the tag is not available). need <?xml version="1.0" encoding="UTF-8"?>

XML with autoclosed tags

I have the following xml:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14490.99" systemVersion="18F132" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
   <entity name="Pessoa" representedClassName="Pessoa" syncable="YES" codeGenerationType="class">
      <attribute name="id" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
   </entity>
</model>

and the structs:

struct Model: Codable, Equatable, DynamicNodeEncoding {
    
    let type: String
    let documentVersion: String
    let lastSavedToolsVersion: String
    let systemVersion: String
    let minimumToolsVersion: String
    let sourceLanguage: String
    let userDefinedModelVersionIdentifier: String
    let entities: [Entity]
    
    enum CodingKeys: String, CodingKey {
        case type
        case documentVersion
        case lastSavedToolsVersion
        case systemVersion
        case minimumToolsVersion
        case sourceLanguage
        case userDefinedModelVersionIdentifier
        case entities = "entity"
    }
    
    static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
        switch key {
        case Model.CodingKeys.type: return .both
        case Model.CodingKeys.documentVersion: return .both
        case Model.CodingKeys.lastSavedToolsVersion: return .both
        case Model.CodingKeys.systemVersion: return .both
        case Model.CodingKeys.minimumToolsVersion: return .both
        case Model.CodingKeys.sourceLanguage: return .both
        case Model.CodingKeys.userDefinedModelVersionIdentifier: return .both
        default: return .element
        }
    }
}

struct Entity: Codable, Equatable, DynamicNodeEncoding {
    
    let name: String
    let representedClassName: String
    let syncable: String
    let codeGenerationType: String
    let attributes: [Attribute]
    
    enum CodingKeys: String, CodingKey {
        case name
        case representedClassName
        case syncable
        case codeGenerationType
        case attributes = "attribute"
    }
    
    static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
        switch key {
        case Entity.CodingKeys.name: return .both
        case Entity.CodingKeys.representedClassName: return .both
        case Entity.CodingKeys.syncable: return .both
        case Entity.CodingKeys.codeGenerationType: return .both
        default: return .element
        }
    }
    
}

struct Attribute: Codable, Equatable, DynamicNodeEncoding {
    
    let name: String
    let optional: String
    let attributeType: String
    let defaultValueString: String
    let userScalarValueType: String
    let syncable: String
    
    enum CodingKeys: String, CodingKey {
        case name
        case optional
        case attributeType
        case defaultValueString
        case userScalarValueType
        case syncable
    }
    
    static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
        switch key {
        case Attribute.CodingKeys.name: return .both
        case Attribute.CodingKeys.optional: return .both
        case Attribute.CodingKeys.attributeType: return .both
        case Attribute.CodingKeys.defaultValueString: return .both
        case Attribute.CodingKeys.userScalarValueType: return .both
        case Attribute.CodingKeys.syncable: return .both
        default:
            return .element
        }
    }
    
}

when I ran the decode, I get the error:

Fatal error: Unrecognized top-level element of type: NullBox: file

and it's related to the attributes array in Entity, could you help me please?

nodeEncodingStrategy

Would like to see if it would be possible to look at extending the definition of the custom node encoding strategy. Or create another custom type

        case custom((Encodable.Type, Encoder) -> ((CodingKey) -> XMLEncoder.NodeEncoding))

What would be nice is if we can provide the Value.

        case custom((Encodable.Type, Encoder) -> ((Value) -> XMLEncoder.NodeEncoding))

here is the use case. Given a node type may have attributes using different keys names, I have created a AttributeBox.

public struct Attribute<Value> {

    public let value: Value

    public init(_ value: Value) {
        self.value = value
    }
}

extension Attribute: Encodable where Value: Encodable {
    public func encode(to encoder: Encoder) throws {
        try self.value.encode(to: encoder)
    }
}

extension Attribute: Decodable where Value: Decodable {
    public init(from decoder: Decoder) throws {
        self.init(try Value(from: decoder))
    }
}

So it would be nice to be able to say for Node Activity use the Type Attribute<Any> as the attribute

iso8601 formatter with RFC3339 is too restrictive

let formatter = ISO8601DateFormatter()
formatter.formatOptions = .withInternetDateTime

this formatOption restricts to RFC3339 format. That format requires timezone. Sometimes the timezone is not included, therefore results in error.
I manage to create a custom formatter instantiating
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withColonSeparatorInTime, .withDashSeparatorInDate, .withFullDate, .withTime]
which supports strings without timezone, but for strings with timezone, the timezone is lost in the resulting date.

would be good that the parser will be more liberal in accepting date strings

OutputFormatting = .sortedKeys

I believe there is no reason for @available in (legacy code from JSONEncoder?)
@available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *)
public static let sortedKeys = OutputFormatting(rawValue: 1 << 1)

plist missing CFBundleVersion - fails AppStore Connect upload

Using carthage to either grab the latest prebuilt binary 0.3.0 or forcing carthage to build the latest XmlCoder results in the framework's plist missing a value for CFBundleVersion, which is required for AppStore Connect upload. Other plist keys are correctly filled in. I see it's using the $(CURRENT_PROJECT_VERSION) variable for CFBundleVersion. Perhaps that is not resolving when built with carthage? Also - looking at the project.pbxproj file - I do not see a project version at all

AppStore error: ERROR ITMS-90056: XMLCoder.framework is invalid. The Info.plist file is missing the required key: CFBundleVersion

EXC_BAD_ACCESS when running tests

When I download the current master branch from scratch and run the tests on the 13.2.2 simulator (from Xcode 11.2.1 (11B500)), it crashes with EXC_BAD_ACCESS. Any idea what's going wrong?

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x12064a034)
    frame #0: 0x000000012064a034 XMLCoderTests
    frame #1: 0x00000001205af954 XMLCoderTests`XMLKeyedDecodingContainer.init(decoder=0x00007fe2a2f9d4c0, container=0x00007fe2a2fa5680) at XMLKeyedDecodingContainer.swift:37:19
    frame #2: 0x00000001205a4afd XMLCoderTests`XMLDecoderImplementation.keyedContainer<Key>(self=0x00007fe2a2f9d4c0) at XMLDecoderImplementation.swift:111:72
    frame #3: 0x00000001205a3e9a XMLCoderTests`XMLDecoderImplementation.container<Key>(keyType=XMLCoderTests.Foo.CodingKeys, self=0x00007fe2a2f9d4c0) at XMLDecoderImplementation.swift:77:24
    frame #4: 0x00000001205a712b XMLCoderTests`protocol witness for Decoder.container<A>(keyedBy:) in conformance XMLDecoderImplementation at <compiler-generated>:0
    frame #5: 0x0000000120dfd158 libswiftCore.dylib`dispatch thunk of Swift.Decoder.container<A where A1: Swift.CodingKey>(keyedBy: A1.Type) throws -> Swift.KeyedDecodingContainer<A1> + 8
    frame #6: 0x00000001203cd5c2 XMLCoderTests`Foo.init(decoder=0x00007fe2a2f9d4c0) at <compiler-generated>:0
    frame #7: 0x00000001203ce5c3 XMLCoderTests`protocol witness for Decodable.init(from:) in conformance Foo at <compiler-generated>:0
    frame #8: 0x0000000120dfd0f7 libswiftCore.dylib`dispatch thunk of Swift.Decodable.init(from: Swift.Decoder) throws -> A + 7
    frame #9: 0x00000001205ad950 XMLCoderTests`XMLDecoderImplementation.unbox<T>(box=XMLCoder.KeyedBox @ 0x00007fe2a2fb06c0, self=0x00007fe2a2f9d4c0) at XMLDecoderImplementation.swift:457:36
    frame #10: 0x000000012059e815 XMLCoderTests`XMLDecoder.decode<T>(type=XMLCoderTests.Foo, data=62 bytes, self=0x00007fe2a2f42ba0) at XMLDecoder.swift:369:28
  * frame #11: 0x00000001203d467f XMLCoderTests`AttributedIntrinsicTest.testDecode(self=0x00007fe2a2c929e0) at AttributedIntrinsicTest.swift:209:32
    frame #12: 0x00000001203d54ca XMLCoderTests`@objc AttributedIntrinsicTest.testDecode() at <compiler-generated>:0
    frame #13: 0x000000010aac456c CoreFoundation`__invoking___ + 140
    frame #14: 0x000000010aac4440 CoreFoundation`-[NSInvocation invoke] + 320
    frame #15: 0x0000000109ed4a21 XCTest`__24-[XCTestCase invokeTest]_block_invoke.208 + 78
    frame #16: 0x0000000109f2eb49 XCTest`-[XCTestCase(Failures) performFailableBlock:testCaseRun:shouldInterruptTest:] + 57
    frame #17: 0x0000000109f2ea67 XCTest`-[XCTestCase(Failures) _performTurningExceptionsIntoFailuresInterruptAfterHandling:block:] + 96
    frame #18: 0x0000000109ed450a XCTest`__24-[XCTestCase invokeTest]_block_invoke + 1153
    frame #19: 0x0000000109ed3fe2 XCTest`-[XCTestCase testContextPerformInScope:] + 211
    frame #20: 0x0000000109ed407c XCTest`-[XCTestCase invokeTest] + 137
    frame #21: 0x0000000109ed5ded XCTest`__26-[XCTestCase performTest:]_block_invoke_2 + 43
    frame #22: 0x0000000109f2eb49 XCTest`-[XCTestCase(Failures) performFailableBlock:testCaseRun:shouldInterruptTest:] + 57
    frame #23: 0x0000000109f2ea67 XCTest`-[XCTestCase(Failures) _performTurningExceptionsIntoFailuresInterruptAfterHandling:block:] + 96
    frame #24: 0x0000000109ed5d04 XCTest`__26-[XCTestCase performTest:]_block_invoke.334 + 88
    frame #25: 0x0000000109f42352 XCTest`+[XCTContext runInContextForTestCase:block:] + 219
    frame #26: 0x0000000109ed5473 XCTest`-[XCTestCase performTest:] + 668
    frame #27: 0x0000000109f1a997 XCTest`-[XCTest runTest] + 57
    frame #28: 0x0000000109ecfa4a XCTest`__27-[XCTestSuite performTest:]_block_invoke + 365
    frame #29: 0x0000000109ecf174 XCTest`-[XCTestSuite _performProtectedSectionForTest:testSection:] + 54
    frame #30: 0x0000000109ecf471 XCTest`-[XCTestSuite performTest:] + 355
    frame #31: 0x0000000109f1a997 XCTest`-[XCTest runTest] + 57
    frame #32: 0x0000000109ecfa4a XCTest`__27-[XCTestSuite performTest:]_block_invoke + 365
    frame #33: 0x0000000109ecf174 XCTest`-[XCTestSuite _performProtectedSectionForTest:testSection:] + 54
    frame #34: 0x0000000109ecf471 XCTest`-[XCTestSuite performTest:] + 355
    frame #35: 0x0000000109f1a997 XCTest`-[XCTest runTest] + 57
    frame #36: 0x0000000109ecfa4a XCTest`__27-[XCTestSuite performTest:]_block_invoke + 365
    frame #37: 0x0000000109ecf174 XCTest`-[XCTestSuite _performProtectedSectionForTest:testSection:] + 54
    frame #38: 0x0000000109ecf471 XCTest`-[XCTestSuite performTest:] + 355
    frame #39: 0x0000000109f1a997 XCTest`-[XCTest runTest] + 57
    frame #40: 0x0000000109f512fe XCTest`__44-[XCTTestRunSession runTestsAndReturnError:]_block_invoke + 171
    frame #41: 0x0000000109f51401 XCTest`__44-[XCTTestRunSession runTestsAndReturnError:]_block_invoke.84 + 118
    frame #42: 0x0000000109ee9793 XCTest`-[XCTestObservationCenter _observeTestExecutionForBlock:] + 588
    frame #43: 0x0000000109f510bd XCTest`-[XCTTestRunSession runTestsAndReturnError:] + 623
    frame #44: 0x0000000109eb2fd7 XCTest`-[XCTestDriver runTestsAndReturnError:] + 456
    frame #45: 0x0000000109f3e5a4 XCTest`_XCTestMain + 2484
    frame #46: 0x0000000109381c1b xctest`main + 267
    frame #47: 0x000000010e4e3d81 libdyld.dylib`start + 1
    frame #48: 0x000000010e4e3d81 libdyld.dylib`start + 1

Decode/Encode comments from XML?

Hi,

I went through the test examples, although they are thorough I didn't see any examples about how to handle comments. So do you have a recommendation on how do that?

sample XML:

<root>
   <-- this is the header comment -->
   <fancy_header/>
</root>

I convert from XML to some other type then back to XML so I am trying to conserve the original comments.

Thanks for the help

Specify starting node for decoding

Perhaps I am overlooking this, but I think it would be useful if the starting node could be specified in some way, perhaps via XPath. While trying to implement an RSS feed, I found that I either have to implement the parent node to get to the child nodes I am interested in or write my own init(from decoder: Decoder).

For example:

<?xml version="1.0"?>
<rss version="2.0">
  <channel>
    <title>Title</title>
…
  </channel>
</rss>

To get to the title node, I need to implement a type for the channel node as well:

struct RSSFeed: Decodable {
  let channel: Channel

  struct Channel: Decodable {
    let title: String
  }
}

XmlEncoder: ordering of elements

Hi,
is there any possibility to set the order of attributes in an element while encoding a model-instance?
I'm trying to build a GPX reader/writer with this little awesome framework and have noticed that the order of my attributes in an element varies from encode to encode. Am I missing something?

Thanks in advance

Encoding a CDATA wrapped object

I have a question around producing the following output:

<MsgData><![CDATA[
<foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="TOKEN">
<someElement>1111.2222.3333.4444</someElement>
<anotherElement>4000000000000065</anotherElement>
<test>31122019</test>
</foo>]]></MsgData>

How can I use XMLEncoder to produce? This element is nested within others but they've been omitted for clarity. All I can currently think of is encode the internal object separately, convert from Data into a String, and then encode with a CDATA encoding strategy.

I'm also slightly confused around how to conditionally use CData encoding on strings without enabling and disabling it on the encoder as I pass. Any thoughts or ideas would be hugely appreciated!

Option for XMLEncoder not to self-close tags?

Hi I try to find ways to make XMLEncoder NOT to produce self-closing tags.

i.e. I would like <tag id="1"></tag> rather than <tag id="1" />. Do we have a way to customize this?

Thank you

Question: ways to parse <!DOCTYPE>

It may not be as important as my special use case, I want to parse MusicXML, whose top-level element might either be score-partwise or score-timewise, which is determined by the <!DOCTYPE> meta-element, an example shown below.

<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">

Wondering if there's any support for decoding <!DOCTYPE>. If not, what change you think might need to support this. Very appreciated.

Empty elements fail to decode to optionals in 0.3.0

After upgrading to 0.3.0, XML elements that are empty and mapped to an optional String fail to decode. Rolling back to 0.2.1 works.

Example struct:

struct BarcodeData : Decodable {
    var type = 0
    var barcodeType: String?
    var length = 0
    var barcode = ""
    
    private enum CodingKeys: String, CodingKey {
        case type = "Type"
        case barcodeType = "BarcodeType"
        case length = "Length"
        case barcode = "Barcode"
    }
    
    init() {}
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.type = try container.decode(Int.self, forKey: .type)
        if container.contains(.barcodeType) {
            self.barcodeType = try container.decode(String.self, forKey: .barcodeType)
        }
        self.length = try container.decode(Int.self, forKey: .length)
        self.barcode = try container.decode(String.self, forKey: .barcode)
    }
}

Example data:
<BarcodeData><Type>67</Type><BarcodeType></BarcodeType><Length>4</Length><Barcode>1112</Barcode></BarcodeData>

In version 0.3.0 decoding results in this error:
Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "BarcodeType", intValue: nil)], debugDescription: "Expected String value but found null instead.", underlyingError: nil))

In 0.2.1 decoding simply leaves the optional String nil like expected

Extra level of indirection?

Moving away from NSXMLParser (Objective-c) in my app to Decodable makes this library very handy. Thank you for forking and maintaining it.

I have a Goodreads XML response which I'm decoding with XMLCoder. That works great.

goodreads.xml.txt
Goodreads.swift.txt
GoodreadsTests.swift.txt

However, there is a level of indirection after following the books example which is bothering me a bit.

In the Book class I need to add this:

var authors: AuthorList

instead of this:

var authors: [Author]

where AuthorList is defined as:

struct AuthorList: Decodable {
  var authors: [Author]
  enum CodingKeys: String, CodingKey {
    case authors = "author"
  }
}

If I don't do that, I get a parsing error:

keyNotFound(CodingKeys(stringValue: "id", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "book", intValue: nil), CodingKeys(stringValue: "authors", intValue: nil), _XMLKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"id\", intValue: nil) (\"id\").", underlyingError: nil))
/Users/tony/Downloads/XMLCoder-master/Tests/XMLCoderTests/GoodreadsTests.swift:24: error: -[XMLCoderTests.GoodreadsTests testGoodreadsBundle] : XCTAssertNotNil failed - error retrieving book

When using var authors: [Author] I added a case to codingKeys in Book:

case authors = "author"

to no avail.

Of course the hack forces me to do this in the test case:

let authors = book?.authors.authors.map { $0 }

instead of this:

let authors = book?.authors.map { $0 }

Any hints to remove this extra level of indirection would be very much appreciated.

Thanks a lot.

Arrays of enums

I am trying to decode this XML:

<?xml version="1.0" encoding="UTF-8"?>
<container>
    <p>
        <run>
            <id>1518</id>
            <text>I am answering it again.</text>
        </run>
        <properties>
            <id>431</id>
            <title>A Word About Wake Times&#xD;</title>
        </properties>
        <br />
    </p>
    <p>
        <run>
            <id>1519</id>
            <text>I am answering it again.</text>
        </run>
    </p>
</container>

This is my implementation:

struct Run: Decodable {
    let id: Int
    let text: String
}

struct Properties: Decodable {
    let id: Int
    let title: String
}

struct Break: Decodable {
    
}

enum Entry: Decodable {
    case run(Run)
    case properties(Properties)
    case br(Break)
    
    private enum CodingKeys: String, CodingKey {
        case run, properties, br
    }
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do {
            self = .run(try container.decode(Run.self, forKey: .run))
        } catch DecodingError.keyNotFound {
            do {
                self = .properties(try container.decode(Properties.self, forKey: .properties))
            } catch DecodingError.keyNotFound {
                self = .br(try container.decode(Break.self, forKey: .br))
            }
        }
    }
}

struct Paragraph: Decodable {
    let entries: [Entry]
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        entries = try container.decode([Entry].self)
    }
}

do {
    let result = try XMLDecoder().decode([Paragraph].self, from: data)
    for paragraph in result {
        print("Paragraph :")
        for entry in paragraph.entries {
            switch entry {
            case .run(let run):
                print("Run : \(run)")
            case .properties(let properties):
                print("Properties : \(properties)")
            case .br(let br):
                print("Break : \(br)")
            }
        }
        print("End paragraph")
    }
}
catch {
    print(error)
}

But I am getting an odd DecodingError.keyNotFound

I know that there is already an issue about enums but I think this one is different because I am trying to do an array of array of enums and I don't get the same error.

I am using an enum because I want to be able to decode a <p> with different optional elements and keep the order in which they are.

Can you tell me if this is a bug from the library or from my own code? Can achieve what I want to do by some other way?

Thank you!

UnkeyedEncodingContainer results in incorrect XML string

There's some bugs hadling ordered arrays i.e. unkeyed containers.
Let's say, I have a tree with some nodes.

let tree = TreeNode()

// create sub node with a child
let subNode1 = TreeNode()
let subSubNode1 = TreeNode()
subNode1.addChild(subSubNode1)

// create another sub node with a child
let subNode2 = TreeNode()
let subSubNode2 = TreeNode()
subNode2.addChild(subSubNode2)

// add children to that child
let subSubSubNode1 = TreeNode()
let subSubSubNode2 = TreeNode()
subSubNode2(subSubSubNode1)
subSubNode2(subSubSubNode2)

// now add all of that to the tree
tree.addChild(subNode1)
tree.addChild(subNode2)

Now I'm encoding the tree as array of arrays.

func encode(to encoder: Encoder) throws {
	var nodeContainer = encoder.container(keyedBy: OutlineTree.CodingKeys.self)
	try nodeContainer.encode(element, forKey: .node)
	
	guard !children.isEmpty else { return }
	
	var childrenContainer = nodeContainer.nestedUnkeyedContainer(forKey: .children)
	try children.forEach{ try childrenContainer.encode($0) }
}

The tree is stored in some Project object which is encoded like this:

public func encode(to encoder: Encoder) throws {
	var container = encoder.container(keyedBy: CodingKeys.self)
	try container.encode(identifier, forKey: .identifier)
	var contentContainer = container.nestedUnkeyedContainer(forKey: .content)
	try content.rootTree.children.forEach{ try contentContainer.encode($0) }
}

So, here's the problem. If I use builtin JSONEncoder everything is fine. I get this string:

{
  "identifier" : "7A71AE91-D0F6-4837-9E66-3C9E4FF06702",
  "content" : [
    {
      "node" : {
        "type" : "subNode1",
      },
      "children" : [
        {
          "node" : {
            "type" : "subSubNode1",
          }
        }
      ]
    },
    {
      "node" : {
        "type" : "subNode2",
      },
      "children" : [
        {
          "node" : {
            "type" : "subSubNode2",
          },
          "children" : [
            {
              "node" : {
                "type" : "subSubSubNode1",
              }
            },
            {
              "node" : {
                "type" : "subSubSubNode2",
              }
            }
          ]
        }
      ]
    }
  ]
}

But if I'm using the XMLEncoder, I'm gettin the messed up string:

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <content>
        <node type="subNode1" />
        <children>
            <node type="subSubNode1" />
        </children>
    </content>
    <content>
        <node type="subNode2" />
        <children>
            <node type="subSubNode2" />
            <children>
                <node type="subSubSubNode1" />
            </children>
            <children>
                <node type="subSubSubNode2" />
            </children>
        </children>
    </content>
</project>

It creates a key for every item in an array which is wrong, as far as I understand. It should create a key for an unkeyed container as a whole instead of keying every item in the container with that key.

P.S.: that XML string is impossible to decode correctly.

Decode special XML Structure

Hi!

How can I decode such an xml? All server responses always come with the structure

<response>
<values>
....
</values>
</response>

Example XML:

<?xml version="1.0" encoding="utf-8"?>
<response>
  <values>
    <id>34343434343</id>
    <alias>Lorem</alias>
    <stealth>0</stealth>
    <mailing_accept>0</mailing_accept>
    <email>[email protected]</email>
    <avatar>https://lh3.googleusercontent.com/a-/dsaasdsdasdasdas</avatar>
    <number_of_tests>56</number_of_tests>
    <points>-1</points>
    <average_score>1.33</average_score>
    <premium>1</premium>
  </values>
  <result>1</result>
  <timeout>31</timeout>
</response>

Thnks a lot!

Float vs Float64=Double not parsing 3.14

if string = "3.14" (which is a valid Float literal), and Codable class contains var myFloat: Float?, on MacOSX 10.14, it does not parse. If I change "typealias Unboxed = Float64" (Float64 seems to be Double from Apple) to Float like: "typealias Unboxed = Float" in FloatBox.swift , then parsing works.

The order of the attributes changes randomly

I noticed that when there are multiple attributes, every time I encode the model they are ordered in a different way in the resulting xml. This is a huge problem during unit tests, where it becomes impossible to validate the encoded Data (or String) against a reference one.

Is this a known issue?

Thanks,
Andres

Fix nested attributed intrinsic

As reported in #12 by @thecb4:

A standalone XML element with attributed intrinsic works. When it's embedded in another element, it fails.

// This passes
  let previewTime = 
  """
  <?xml version="1.0" encoding="UTF-8"?>
  <preview_image_time format="24/999 1000/nonDrop">00:00:17:01</preview_image_time>
  """.data(using: .utf8)!


  let decoder = XMLDecoder()
  decoder.errorContextLength = 10

  let metadata1 = try decoder.decode(PreviewImageTime.self, from: previewTime)
  print(metadata1)

// this does not pass
  let preview = 
  """
  <?xml version="1.0" encoding="UTF-8"?>
  <app_preview display_target="iOS-6.5-in" position="1">
    <preview_image_time format="24/999 1000/nonDrop">00:00:17:01</preview_image_time>
  </app_preview>
  """.data(using: .utf8)!

  let metadata2 = try decoder.decode(AppPreview.self, from: preview)
  print(metadata2)

structs:

struct AppPreview: Codable {
  var displayTarget: String
  var position: Int
  var previewImageTime: PreviewImageTime
  // var dataFile: DataFile

  enum CodingKeys: String, CodingKey {
    case displayTarget = "display_target"
    case position
    case previewImageTime = "preview_image_time"
    // case dataFile = "data_file"
  }
}

struct PreviewImageTime: Codable, DynamicNodeEncoding {

  var format: String
  var value: String

  enum CodingKeys: String, CodingKey {
    case format
    case value
  }

  static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding {
    print("node encoding")
    switch key {
      case CodingKeys.format:
        return .attribute
      default:
        return .element
    }
  }
}

Generate and publish API docs on CI

XMLCoder should have publicly visible docs for its API. First option to consider for auto-generated docs is Jazzy. Generated docs could be published with GitHub Pages.

Move custom build settings to Package.xcconfig

This would allow us to remove .xcodeproj and to avoid conflicts as mentioned in #19. This would probably require moving benchmark baselines to a separate directory and to add a script that regenerates the project file and copies baselines to a correct location.

Report error context with a customisable length

As reported in CoreOffice/CoreXLSX#12 there are multiple issues where it's hard to understand why parsing of a certain XML has failed. XMLCoder should be able to report a snippet of a given XML of specified length around the position where the error has happened. This should be an property of type Int? named errorContextLength on XMLDecoder instance. An error thrown from XML parser that has line/column location should be repackaged having a context around that location of specified length.

For example, if an error was thrown indicating that there's an unexpected character at line 3, column 15 with errorContextLength set to 10, a new error type should be rethrown containing 5 characters before column 15 and 5 characters after, all on line 3. Line wrapping should be handled correctly too as the context can span more than a few lines.

Root level attributes don't get encoded back to attribute when converting back to XML file from Plist

Hi,

First of all thanks for creating such a useful library! I am having a bit of a hard time with root-level attribute encoding.

I have a complex XML that requires a big model but to simply:

To decode something this:

<?xml version="1.0" encoding="UTF-8"?>
<policy name = "..." attr2="...">
   <Initial>
    more xml here
     .....
     ....
  </Initial>
</policy>

I am doing

struct Policy: Codable, Equatable, DynamicNodeEncoding {
    var name: String
    var initial: Initial
enum CodingKeys: String, CodingKey {
        case name,  initial
    }

static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
        switch key {
        case CodingKeys.name
            return .attribute
        default:
            return .element
        }
    }
}

Then I decode using XMLDecoder(), encode to PLIST using PropertyListEncoder() then encode back to XML with first PropertyListDecoder() and then

            let data = try encoder.encode(modifiedData, withRootKey: "policy",
                                          header: XMLHeader(version: 1.0,
                                                            encoding: "UTF-8"))

and everything works gets encoded properly except the root level attributes.
The result becomes:

<policy><name>generic</name>
<initial>
   .....
   .....
</initial>
</policy>

I tried .both instead of just encoding as .attribute but that did not make a difference.

Any ideas on how to fix this?

Thanks

Arrays of one element encode ok but fail to decode

With a simple struct:
struct ProudParent : Codable {
var myChildAge: [Int]
}

var theparent = ProudParent()
theparent.myChildAge = [2,3]

<ProudParent><myChildAge>2</myChildAge><myChildAge>3</myChildAge></ProudParent>

The encoding always works, but if there is only one element in the myChildrenAgesCollection Int array it will fail to decode with the error: "Expected to decode Array but found a string/data instead".

EX: this will encode but fail to decode back
var theparent = ProudParent()
theparent.myChildAge = [2]

<ProudParent><myChildAge>2</myChildAge></ProudParent>

Surely something as simple as that structure can be decoded. What trick am I missing?

Can’t reach an XML value

Hi there! I can’t find the right data structure to decode the following XML string:

<something name="Some name">Some value</something>

I ran out of ideas. May anyone give me a hint?

Thanks!

<element /> issue?

This might be user error, or might be related to #101 not really sure. I am trying to decode:

<?xml version="1.0" encoding="UTF-8"?>
<compendium>
  <foobar>
    <foo>Foo string value</foo>
    <nested>
      <name>Name</name>
      <text>Bar string value</text>
    </nested>
    <nested>
      <name>Name</name>
      <text>Bar string value</text>
      <text />
      <text>Bar string value</text>
    </nested>
  </foobar>
</compendium>

My structs:

           struct NestedObject : Codable {
                var name : String?
                var text : [String]?
            }
            
            struct Foobar : Codable {
                var foo : String?
                var nested : [NestedObject]?
            }
            
            struct Compendium: Codable  {
                var foobars: [Foobar]
                enum CodingKeys: String, CodingKey {
                    case foobars = "foobar"
                }
            }

The empty text in the last nested object fails to decode. I think cause the String inside the array isn't optional, so when not there, the decoder freaks out? Not sure...any help would be appreciated!

Array of enums with associated values

Thanks for taking up the maintenance on this project!

I am working with an XML schema which requires support for heterogeneous arrays of values within a set of specified types.

Consider the following enum which defines each of the types allowable:

enum AB {
    struct A: Decodable { let value: Int }
    struct B: Decodable { let value: String }
    case a(A)
    case b(B)   
}

Currently, the Decodable implementation is as follows (based on objc.io/codable-enums):

extension AB: Decodable {
    enum CodingKeys: String, CodingKey { case a, b }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do {
            self = .a(try container.decode(A.self, forKey: .a))
        } catch {
            self = .b(try container.decode(B.self, forKey: .b))
        }
    }
}

A minimal example of the source XML could look something like this (though there could be arbitrary amounts of either type, in any order):

let xml = """
<?xml version="1.0" encoding="UTF-8"?>
<container>
    <a value="42"></a>
    <b value="forty-two"></b>
</container>
"""

Upon decoding it with the XMLDecoder:

let decoder = XMLDecoder()
let abs: [AB] = try! decoder.decode([AB].self, from: xml.data(using: .utf8)!)

We get the following:

// => [.a(AB.A(value: 42))]

The desired result should be:

// => [.a(AB.A(value: 42)), .b(AB.B(value: "forty-two"))]

I have tried many combinations of keyed, unkeyed, singleValue, and nested containers, yet haven't found a solution that treats the data as an Array rather than a Dictionary.

Let me know if I am holding this wrong (at either the Decodable or the XMLDecoder stages), or if this could be a current limitation of the XMLCoder implementation. I'd be happy to contribute, but if anyone notices something glaring here, this would be very helpful.

Decoding containers with (potentially)-empty elements

We have a use case where we need to decode an array of potentially empty elements.

Currently, unkeyed and keyed containers holding onto empty (e.g., a struct with no fields) or a potentially empty (e.g., a struct with all optional fields) elements does not decode.

Say we've got a struct like this:

struct Empty: Equatable, Codable { }

If we have some XML like this:

let xml = """
<container>
    <empty/>
    <empty/>
    <empty/>
</container>
"""

It would be nice to decode like this:

let decoded = try XMLDecoder().decode([Empty].self, from: xml.data(using: .utf8)!)
// Throws "Expected Empty but found null instead."

The poetics are strong.

Wrapping it up a bit, consider this:

struct EmptyArray: Equatable, Codable {
    enum CodingKeys: String, CodingKey { case empties = "empty" }
    let empties: [Empty]
}

Given the xml from above, we can try to decode it like this:

let decoded = try XMLDecoder().decode(EmptyArray.self, from: xml.data(using: .utf8)!)
// Throws "Expected Array<Empty> value but found null instead."

Lastly, given this container:

struct EmptyWrapper {
    let empty: Empty
}

And we have some XML like this:

let xml = """
<wrapper>
    <empty/>
</wrapper>
"""

And we try to decode it like this:

let decoded = try XMLDecoder().decode(EmptyWrapper.self, from: xml.data(using: .utf8)!)
// Throws "Expected Empty value but found null instead"

We get the same outcomes if you substitute the Empty type with something like this:

struct MaybeEmpty {
    var idunno: Int?
}

If this seems like something that should theoretically be supported, I can give a go at it. It smells like it might touch similar neighborhoods of the codebase as #122.

Bad access error when running on device

After working on simulator on my app, I wanted to test it on my iPad but I have a EXC_BAD_ACCESS on line 382 of XMLDecoderImplementation.swift. This error isn't present when I run the app on the simulator.

I run the app on a 2017 iPad with iOS 12.1.4 and for the simulator I used the iPad Pro 11" with iOS 12.2.

Nested enums coding doesn't work

Seems like I found a bug with enums decoding.

Consider the following set of structures describing a book:

struct Book: Codable {
    let title: String
    let chapters: [Chapter]
}

enum Chapter {
    struct Content {
        let title: String
        let content: String
    }

    case intro(Content)
    case body(Content)
    case outro(Content)
}

and the example xml:

let example = """
<?xml version="1.0" encoding="UTF-8"?>
<book title="Example">
    <chapters>
        <intro title="Intro">Content of first chapter</intro>
        <chapter title="Chapter 1">Content of chapter 1</chapter>
        <chapter title="Chapter 2">Content of chapter 2</chapter>
        <outro title="Epilogue">Content of last chapter</outro>
    </chapters>
</book>
"""

let book = try XMLDecoder().decode(Book.self, from: example.data(using: .utf8)!)

I'm getting an object with only one chapter Intro:

Book(title: "Example", 
    chapters: [
        PDF.Chapter.intro(PDF.Chapter.Content(title: "Intro", content: "Content of first chapter"))
    ]
)

But if I modify my example like this:

let example2 = """
<?xml version="1.0" encoding="UTF-8"?>
<chapters>
    <intro title="Intro">Content of first chapter</intro>
    <chapter title="Chapter 1">Content of chapter 1</chapter>
    <chapter title="Chapter 2">Content of chapter 2</chapter>
    <outro title="Epilogue">Content of last chapter</outro>
</chapters>
"""

let book = try XMLDecoder().decode([Chapter].self, from: example2.data(using: .utf8)!)

the result will be a proper array of chapters:

[
PDF.Chapter.intro(PDF.Chapter.Content(title: "Intro", content: "Content of first chapter")), 
PDF.Chapter.body(PDF.Chapter.Content(title: "Chapter 1", content: "Content of chapter 1")), 
PDF.Chapter.body(PDF.Chapter.Content(title: "Chapter 2", content: "Content of chapter 2")), 
PDF.Chapter.outro(PDF.Chapter.Content(title: "Epilogue", content: "Content of last chapter"))
]

Codable extensions implemented like this:

extension Chapter: Codable {
    enum CodingKeys: String, XMLChoiceCodingKey {
        case intro, body = "chapter", outro
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let value = try? container.decode(Content.self, forKey: .body) {
            self = .body(value)
        } else if let value = try? container.decode(Content.self, forKey: .intro) {
            self = .intro(value)
        } else if let value = try? container.decode(Content.self, forKey: .outro) {
            self = .outro(value)
        } else {
            throw BookError.unknownChapterType
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case .intro(let value):
            try container.encode(value, forKey: .intro)
        case .body(let value):
            try container.encode(value, forKey: .body)
        case .outro(let value):
            try container.encode(value, forKey: .outro)
        }
    }
}

extension Chapter.Content: Codable, DynamicNodeEncoding {
    enum CodingKeys: String, CodingKey {
        case title
        case value = ""
    }

    static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
        switch key {
        case CodingKeys.value:
            return .element
        default:
            return .attribute
        }
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        title = try container.decode(String.self, forKey: .title)
        content = try container.decode(String.self, forKey: .value)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(title, forKey: .title)
        try container.encode(content, forKey: .value)
    }
}

Would appreciate any help or suggestion in resolving this issue.

Crash: Range invalid bounds in XMLStackParser.swift

Line 59,

If xmlParser.lineNumber is 0 this will throw

 for i in 0..<xmlParser.lineNumber - 1 {
            errorPosition += lines[i].count
        }

Fix locally with

        let rangeMax = max(xmlParser.lineNumber - 1, 0)
        for i in 0..<rangeMax {

Not sure if this logic if correct though, instead of throwing or skipping the for loop.

Value with copyright symbol © has its preceding whitespace trimmed off even `trimValueWhitespaces` is set to false

See test case added in my commit that verifies this issue
82d9bac

Regardless of nested or not, parsing something like <t>Copyright © 2019 Company, Inc.</t> gets me "Copyright© 2019 Company, Inc" with the preceding whitespace before © trimmed off.

I look for the codebase and found the only place involving trimmingCharacters is in XMLStackParser:114. But later I found that the Foundation method to trim whitespace is not the culprit either.

XCTAssertEqual("Copyright © 2019 Company, Inc.".trimmingCharacters(in: .whitespacesAndNewlines), "Copyright © 2019 Company, Inc.") => true

So I am confused. Any pointer is appreciated.

Trimmed whitespace on decoding String

I had another issue with the library when decoding strings. For example if I have a string with a whitespace at the end in my XML, it will get trimmed when decoding but I don't want this behavior.

Moreover, when an element contains an empty string, it is automatically interpreted as nil.

Is there support for Heterogeneous collections?

I'd love to use this library, just want to make sure there is support for heterogeneous collections before I begin integrating it.

I am getting a response back from an API that is serving two different kinds of responses and I need to be able to decode them both and put them into a single container. I could used a super class but then I would lose all the access to other properties. My original thought is that this might be able to be done with a Protocol of some sort?

Vehicle 1:

<LatestInfoTable>
    <AgencyVehicleName>1921</AgencyVehicleName>
    <RouteShortName>HWB</RouteShortName>
    <BlockID>c8bdfbe2-72e9-4870-81a6-d7cb8cd4263f</BlockID>
    <TripID>e15aed4d-f527-47a7-a4d5-f44fa59838de</TripID>
    <IsTripper>N</IsTripper>
    <PatternName>Hethwood B</PatternName>
    <TripStartTime>2019-04-17T12:45:00-04:00</TripStartTime>
    <LastStopName>Torgersen Hall</LastStopName>
    <StopCode>1114</StopCode>
    <Rank>0</Rank>
    <IsBusAtStop>Y</IsBusAtStop>
    <IsTimePoint>Y</IsTimePoint>
    <LatestEvent>2019-04-17T12:46:35-04:00</LatestEvent>
    <LatestRSAEvent>2019-04-17T12:46:31-04:00</LatestRSAEvent>
    <Latitude>50.22935</Latitude>
    <Longitude>-74.42052</Longitude>
    <Direction>308</Direction>
    <Speed>1</Speed>
    <TotalCount>0</TotalCount>
    <PercentOfCapacity>0</PercentOfCapacity>
</LatestInfoTable>

Vehicle 2:

<LatestInfoTable>
    <AgencyVehicleName>6412</AgencyVehicleName>
    <LatestEvent>2019-04-17T09:33:45-04:00</LatestEvent>
    <Latitude>50.19622</Latitude>
    <Longitude>-74.39557</Longitude>
    <Direction>252</Direction>
    <Speed>6</Speed>
</LatestInfoTable>

I'd like to be able to have something like this

struct Resonse: Decodable {
    var vehicles: [Vehicles] 

    enum CodingKeys: String, CodingKey {
        var vehicles = "LatestInfoTable"
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.