CoreOffice / XMLCoder

Easy XML parsing using Codable protocols in Swift
https://coreoffice.github.io/XMLCoder/
MIT License
795 stars 107 forks source link

Can inheritance be implemented? #159

Closed monibu91 closed 4 years ago

monibu91 commented 4 years ago

I have this xml:

<my:page xmlns:my="http://schemas.myxml" id="a4ccae57-78fd-4232-aaff-67dd1c7f6d31">
    <my:group id="group-1" x="14%" y="24%" width="auto" height="auto" padding-left="5%" padding-right="5%" padding-top="2dp" padding-bottom="2dp" layout="absolute" grid-columns-count="2" justify-content="start-left">
        <my:text id="text-1" x="5dp" y="6dp" width="auto" height="5dp" padding-left="10%" padding-right="5%" padding-top="5dp" padding-bottom="10dp" transform="matrix(a, b, c, d, tx, ty)" text-align="left" text-mode="single-line" horizontal-align="left" vertical-align="top" line-height="1.5" font-size="1.5" font-family="Times New Roman"></my:text>          
        <my:image id="img-1" x="15dp" y="16dp" width="adjust-to-min" height="15%" padding-left="20dp" padding-right="15dp" padding-top="15%" padding-bottom="20%" transform="matrix(a, b, c, d, tx, ty)" part-id="test-img" scale-type="fill" ></my:image>  
        <my:diagram id="diagram-1" x="25%" y="26%" width="adjust-to-max" height="25%" padding-left="20dp" padding-right="35%" padding-top="45dp" padding-bottom="55%" transform="matrix(a, b, c, d, tx, ty)" part-id="test-ink"></my:diagram>           
        <my:embed id="5" x="25%" y="26%" width="adjust-to-max" height="25%" padding-left="20dp" padding-right="35%" padding-top="45dp" padding-bottom="55%" transform="matrix(a, b, c, d, tx, ty)" part-id="test-diagram"></my:embed>           
        <my:group id="group-2" x="24%" y="34%" width="inf" height="inf" padding-left="15%" padding-right="15%" padding-top="5dp" padding-bottom="5dp" layout="flex" grid-columns-count="5" justify-content="distribute-evenly">         
           <my:image id="img-2" x="15dp" y="16dp" width="adjust-to-min" height="15%" padding-left="20dp" padding-right="15dp" padding-top="15%" padding-bottom="20%" transform="matrix(a, b, c, d, tx, ty)" scale-type="fill" part-id="bd738193-46b2-401f-ad3c-998a9d28bd9e"></my:image>               
           <my:diagram id="diagram-2" x="25%" y="26%" width="adjust-to-max" height="25%" padding-left="20dp" padding-right="35%" padding-top="45dp" padding-bottom="55%" transform="matrix(a, b, c, d, tx, ty)" part-id="e0fbdab4-f2e7-4e2e-9126-76f94c025061"></my:diagram>            
         </my:group>
    </my:group>
</my:page>

Which represents content on a page. The page has its properties and one group which is the root node of a tree structure. The nodes of the tree are "blocks". A block is like abstract class because there are no elements in the xml that are "block", but "group" , " text", "image", "diagram" and "embed" are blocks ( they have all the properties a block has (id, x, y, width, height, padding-left, padding-right, padding-top, padding-bottom). So my problem is that i don't find a way to express that group is an array of things that are some children of "block" structure. I have to encode and decode the xml. Here is what i did so far:

import Foundation
import XMLCoder

protocol BlockModel: Codable {
    var id : String { get }

    //properties
    var x : String? { get set }
    var y : String? { get set }

    var width : String? { get set }
    var height : String? { get set }

    var paddingLeft : String? { get set }
    var paddingRight : String? { get set }
    var paddingTop : String? { get set }
    var paddingBottom : String? { get set }
}

struct GroupModel : BlockModel, Codable, DynamicNodeDecoding, DynamicNodeEncoding  {
//    var blocks : [BlockModel] = []

    var id : String

    //properties
    var x : String?
    var y : String?

    var width : String?
    var height : String?

    var paddingLeft : String?
    var paddingRight : String?
    var paddingTop : String?
    var paddingBottom : String?

    var layout: String
    var gridColumnsCount: String
    var justifyContent: String

    enum CodingKeys: String, CodingKey {
        case id,x,y,width, height

        case paddingLeft = "padding-left"
        case paddingRight = "padding-right"
        case paddingTop = "padding-top"
        case paddingBottom = "padding-bottom"

        case gridColumnsCount = "grid-columns-count"
        case justifyContent = "justify-content"

//        case blocks = "my:*"

    }

    static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding {
//        switch key {
//        case CodingKeys.blocks:
//            return .element
//        default:
            return .attribute
//        }
    }

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

}

struct TextModel : BlockModel, Codable, DynamicNodeDecoding, DynamicNodeEncoding  {
    var id : String

    //properties
    var x : String?
    var y : String?

    var width : String?
    var height : String?

    var paddingLeft : String?
    var paddingRight : String?
    var paddingTop : String?
    var paddingBottom : String?

    //text specific
    var textAlign: String
    var textMode: String
    var horizontalAlign: String
    var verticalAlign : String
    var lineHeight : String
    var fontSize : String
    var fontFamily : String

    enum CodingKeys: String, CodingKey {
        case id,x,y,width, height

        case paddingLeft = "padding-left"
        case paddingRight = "padding-right"
        case paddingTop = "padding-top"
        case paddingBottom = "padding-bottom"

        case textAlign = "text-align"
        case textMode = "text-mode"
        case horizontalAlign = "horizontal-align"
        case verticalAlign = "vertical-align"
        case lineHeight = "line-height"
        case fontSize = "font-size"
        case fontFamily = "font-family"

    }

    static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding {
        return .attribute
    }

    static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
        return .attribute
    }

}

struct ImageModel : BlockModel, Codable, DynamicNodeDecoding, DynamicNodeEncoding  {
    var id : String

    //properties
    var x : String?
    var y : String?

    var width : String?
    var height : String?

    var paddingLeft : String?
    var paddingRight : String?
    var paddingTop : String?
    var paddingBottom : String?

    var partId: String
    var scaleType: String

    enum CodingKeys: String, CodingKey {
        case id,x,y,width, height

        case paddingLeft = "padding-left"
        case paddingRight = "padding-right"
        case paddingTop = "padding-top"
        case paddingBottom = "padding-bottom"

        case partId = "r:part-id"
        case scaleType = "scale-type"

    }

    static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding {
        return .attribute
    }

    static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
        return .attribute
    }

}

struct DiagramModel : BlockModel, Codable, DynamicNodeDecoding, DynamicNodeEncoding  {
    var id : String

    //properties
    var x : String?
    var y : String?

    var width : String?
    var height : String?

    var paddingLeft : String?
    var paddingRight : String?
    var paddingTop : String?
    var paddingBottom : String?

    var partId: String

    enum CodingKeys: String, CodingKey {
        case id,x,y,width, height

        case paddingLeft = "padding-left"
        case paddingRight = "padding-right"
        case paddingTop = "padding-top"
        case paddingBottom = "padding-bottom"

        case partId = "r:part-id"
    }

    static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding {
        return .attribute
    }

    static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
        return .attribute
    }

}

struct EmbedModel : BlockModel, Codable, DynamicNodeDecoding, DynamicNodeEncoding  {
    var id : String

    //properties
    var x : String?
    var y : String?

    var width : String?
    var height : String?

    var paddingLeft : String?
    var paddingRight : String?
    var paddingTop : String?
    var paddingBottom : String?

    var partId: String

    enum CodingKeys: String, CodingKey {
        case id,x,y,width, height

        case paddingLeft = "padding-left"
        case paddingRight = "padding-right"
        case paddingTop = "padding-top"
        case paddingBottom = "padding-bottom"

        case partId = "r:part-id"
    }

    static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding {
        return .attribute
    }

    static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
        return .attribute
    }

}

struct PageModel: Codable, DynamicNodeDecoding, DynamicNodeEncoding {
    var xmlns: String
    var id: String //UUID
    var root: GroupModel // Group

    enum CodingKeys: String, CodingKey {
        case id
        case xmlns = "xmlns:my"
        case root = "my:group"
    }

    static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding {
        switch key {
        case CodingKeys.root:
            return .element
        default:
            return .attribute
        }
    }

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

}

extension PageModel {

   static func testPageModel(url: URL) {

        do {

            let data = try Data.init(contentsOf: url)
            print("PageModel xml :")
            print(String(data: data, encoding: .utf8)!)

            let decoder = XMLDecoder()

            let pageModel = try decoder.decode(PageModel.self, from: data)

            print(pageModel)

            let header = XMLHeader(version: 1.0, encoding: "UTF-8")
            let encoder = XMLEncoder()

            encoder.outputFormatting = [.prettyPrinted]
            let returnData = try encoder.encode(pageModel, withRootKey: "my:page", header:header )

            print(String(data: returnData, encoding: .utf8)!)

        } catch {
            print("error in loading data from PageModel xml : \(error)")
        }
    }
}

Do you have any idea how i can do it?

monibu91 commented 4 years ago

I have also tried something like:

enum BlockChild: Equatable {

    case group(GroupModel)
    case text(TextModel)
    case image(ImageModel)
    case diagram(DiagramModel)
    case embed(EmbedModel)
}

extension BlockChild: Codable {
    enum CodingKeys: String, CodingKey {
        case group = "my:group"
        case text = "my:text"
        case image = "my:image"
        case diagram = "my:diagram"
        case embed = "my:embed"
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case let .group(value):
            try container.encode(value, forKey: .group)
        case let .text(value):
            try container.encode(value, forKey: .text)
        case let .image(value):
            try container.encode(value, forKey: .image)
        case let .diagram(value):
            try container.encode(value, forKey: .diagram)
        case let .embed(value):
            try container.encode(value, forKey: .embed)
        }
    }

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

        if let tempGroup = try? container.decode(GroupModel.self, forKey: .group){
            self = .group(tempGroup)
        }
        else if let tempText = try? container.decode(TextModel.self, forKey: .text){
            self = .text(tempText)
        }
        else if let tempImage = try? container.decode(ImageModel.self, forKey: .image){
            self = .image(tempImage)
        }
        else if let tempDiagram = try? container.decode(DiagramModel.self, forKey: .diagram){
            self = .diagram(tempDiagram)
        }
        else {
            let tempEmbed = try container.decode(EmbedModel.self, forKey: .embed)
            self = .embed(tempEmbed)
        }
    }
}

But this give me error:

typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [XMLKey(stringValue: "0", intValue: 0), XMLKey(stringValue: "0", intValue: 0)], debugDescription: "Expected to decode Dictionary<String, Any> but found ChoiceBox instead.", underlyingError: nil))
monibu91 commented 4 years ago

With the master branch my code works, but i was using cocoa-pods which i guess is an older version. So the fix for me was to add XMLCoder as Swift package pointing to master.

MaxDesiatov commented 4 years ago

Sorry for the delay, just having a look at this. A new version of XMLCoder will be tagged soon, which will make the fix available on CocoaPods. Thanks for reporting the issue!

MaxDesiatov commented 4 years ago

Closing as resolved in 0.10