CoreOffice / XMLCoder

Easy XML parsing using Codable protocols in Swift

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

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

how to "skip" certain XML tags / element in a TCX file

mjunkmyjunk opened this issue · comments

commented

I'm working on a fitness app that uses the pod from TcxDataProtocol ( FitnessKit/TcxDataProtocol#10 (comment) ) and having some issues in decoding some TCX files due to the way how TCXs are formatted across different sites.

eg: (From Strava)

<TrainingCenterDatabase 
xsi:schemaLocation="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2 http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd" 
xmlns:ns2="http://www.garmin.com/xmlschemas/UserProfile/v2" 
xmlns:ns3="http://www.garmin.com/xmlschemas/ActivityExtension/v2" 
xmlns:ns5="http://www.garmin.com/xmlschemas/ActivityGoals/v1" 
xmlns="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

From Garmin

<TrainingCenterDatabase
  xsi:schemaLocation="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2 http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd"
	xmlns:ns5="http://www.garmin.com/xmlschemas/ActivityGoals/v1"
	xmlns:ns3="http://www.garmin.com/xmlschemas/ActivityExtension/v2"
	xmlns:ns2="http://www.garmin.com/xmlschemas/UserProfile/v2"
	xmlns="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:ns4="http://www.garmin.com/xmlschemas/ProfileExtension/v1">

From: RideWithGPS

<TrainingCenterDatabase 
xmlns="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://www.garmin.com/xmlschemas/ProfileExtension/v1 
http://www.garmin.com/xmlschemas/UserProfilePowerExtensionv1.xsd 
http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2 
http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd 
http://www.garmin.com/xmlschemas/UserProfile/v2 
http://www.garmin.com/xmlschemas/UserProfileExtensionv2.xsd">

As you can see, the headers are all slightly different.

This is what the current CodingKeys look like and if I were to manually comment out the missing cases, then the decoding process will go thru. I'm hoping to get some pointers as to how to make this decoding process a little bit more flexible.

https://github.com/FitnessKit/TcxDataProtocol/blob/master/Sources/TcxDataProtocol/Elements/TrainingCenterDatabase.swift

    /// Coding Keys
    enum CodingKeys: String, CodingKey {
        case schemaLocation = "xsi:schemaLocation"

        case xmlnsNs2 = "xmlns:ns2"
        case xmlnsNs3 = "xmlns:ns3"
        case xmlnsNs4 = "xmlns:ns4"
        case xmlnsNs5 = "xmlns:ns5"

        case xmlns = "xmlns"
        case xmlnsXsi = "xmlns:xsi"

        case activities = "Activities"
        case author = "Author"
    }
}

extension TrainingCenterDatabase: DynamicNodeEncoding {
    
    public static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
        switch key {
        case TrainingCenterDatabase.CodingKeys.schemaLocation:
            return .attribute
        case TrainingCenterDatabase.CodingKeys.xmlnsNs2:
            return .attribute
        case TrainingCenterDatabase.CodingKeys.xmlnsNs3:
            return .attribute
        case TrainingCenterDatabase.CodingKeys.xmlnsNs4:
            return .attribute
        case TrainingCenterDatabase.CodingKeys.xmlnsNs5:
            return .attribute
        case TrainingCenterDatabase.CodingKeys.xmlns:
            return .attribute
        case TrainingCenterDatabase.CodingKeys.xmlnsXsi:
            return .attribute
        default:
            return .element
        }
    }
}

Thanks.

Attached is an example file.
zzz_Demo(STRAVA)-copy.tcx.zip

This is the error

TCXDecode Error:keyNotFound(CodingKeys(stringValue: "xmlns:ns4", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "xmlns:ns4", intValue: nil)], debugDescription: "No attribute or element found for key CodingKeys(stringValue: \"xmlns:ns4\", intValue: nil) (\"xmlns:ns4\").", underlyingError: nil))

I have a somewhat silly suggestion, but I hope it's somewhat reasonable from a certain perspective. Do you actually have to ready any data from these attributes? If not, the simplest approach would be to remove them from your coding keys and the list of properties on the type you're decoding into.

If you do need values of these attributes, would you mind explaining how you intend to use them?

commented

Its not actually a silly idea. Its just that I'm at the point where it's a "I don't know".

As Far as I can tell, there is no actual usage of these data/tags at all. At least for my app's usage, I do not care about them at all, but the upstream Pod (TcxDataProtocol) is/has them as part of their package (which I'm using) and will exit w/ error.

There's also other instances within the XML file which I do use. (I'm pulling the Watts) which is part of the Extensions tag.

eg: variation No.1

	  <Extensions>
		<ns3:TPX>
	 	    <ns3:Speed>0.0</ns3:Speed>
		   <ns3:Watts>84</ns3:Watts>
	     </ns3:TPX>
	</Extensions>

eg: variation No.2

    <Extensions>
       <TPX xmlns="http://www.garmin.com/xmlschemas/ActivityExtension/v2">
        <Speed>0.0</Speed>
        <Watts>84</Watts>
       </TPX>
    </Extensions>

But I noticed that these do not fail. As these are handled this way (which generally just ignores the <TPX xmlns> or <ns3:TPX> tag
https://github.com/FitnessKit/TcxDataProtocol/blob/dd1234588067923bcf38e0eb39b74aee4c5c2c14/Sources/TcxDataProtocol/TcxExtensions/ActivityExtension.swift#L117

    /// Coding Keys
    public enum CodingKeys: String, CodingKey {
        case speed = "ns3:Speed"
        case runCadence = "ns3:RunCadence"
        case watts = "ns3:Watts"

        //attribute
        case cadenceSensor = "ns3:CadenceSensorType"
    }

    /// Alternate Coding Keys
    public enum AlternateCodingKeys: String, CodingKey {
        case speed = "Speed"
        case runCadence = "RunCadence"
        case watts = "Watts"

        //attribute
        case cadenceSensor = "CadenceSensorType"
    }

Yes, namespace prefix is stripped by default, you'll have to set shouldProcessNamespaces to true if you'd like the parser to be namespace-sensitive

commented

Thanks for the link.

Is there any capability to address the instances of ignoring / skipping these tags? Like a "ignoreIfNotPresent"?

Did you try making their corresponding properties optional?

Ignoring and skipping attributes is only possible if you make properties that match them optional or remove them completely. Otherwise it's unclear what the value of that property should be if an attribute is not present.

commented

Could you provide guidance on how to make them "optional"
The possible issue that I foresee if that these conditions can also influence the generation (encoding) of the data into a TCX file. (Its entirely possible that a malformed Header XMLxx tags would render these TCXs not valid and can't be accepted into these Garmin / Strava and others sites.

I will need to see how exactly your types and properties into which you decode these attributes look like to provide such guidance. Would you be able to share a more complete example of Swift types that you use for decoding? Snippets with only coding keys aren't enough.

commented

Thanks!
The Upstream pod which I'm using is this -> https://github.com/FitnessKit/TcxDataProtocol
The example TCX file I have shared in a zip file -> https://github.com/MaxDesiatov/XMLCoder/files/7104615/zzz_Demo.STRAVA.-copy.tcx.zip

For decoding, I'm essentially using these lines (from the TcxDataProtocol website)

let tcxUrl = URL(fileURLWithPath: "TestFile" + ".tcx")
let tcxData = try? Data(contentsOf: tcxUrl)

if let tcxData = tcxData {
    let tcxFile = try? TcxFile.decode(from: tcxData)
}

I'm not exactly sure what you mean by "share a more complete example of Swift types that you use for decoding" aa I'm basically just using the pod and doing the decoding per the readme.

Note: I do realise that this is basically not an issue w/ XMLCoder, but quite possibly how the upstream pod is handling the TCX/XML, but I would like to learn and get this settled. 🙏

Right, I didn't realize that library was involved. I think they should update their code here https://github.com/FitnessKit/TcxDataProtocol/blob/master/Sources/TcxDataProtocol/Elements/TrainingCenterDatabase.swift#L34-L53

to something like

    /// XSI Schema Location
    var schemaLocation: String? = "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2 http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd"

    /// UserProfile Schema
    var xmlnsNs2: String? = "http://www.garmin.com/xmlschemas/UserProfile/v2"

    /// ActivityExtension Schema
    var xmlnsNs3: String = "http://www.garmin.com/xmlschemas/ActivityExtension/v2"

    /// ProfileExtension Schema
    var xmlnsNs4: String? = "http://www.garmin.com/xmlschemas/ProfileExtension/v1"

    /// ActivityGoals Schema
    var xmlnsNs5: String? = "http://www.garmin.com/xmlschemas/ActivityGoals/v1"

    /// TrainingCenterDatabase Schema
    var xmlns: String? = "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2"

    /// Schema
    var xmlnsXsi: String? = "http://www.w3.org/2001/XMLSchema-instance"

That would make it use optional String? instead of String and hopefully will make it work with your variety of files.

commented

this is a "duh" moment for me. When you mean optional`` I didn't even realise it was going to be as simple as putting the ?into theString?```

I just tried this and it worked for a variety of files that I threw at the decoder. Strava and Tapiriik.Sync works. (Ride with GPS has weird structure and doesn't have element tags at all. Since I'm not entirely sure what would happen if I made ALL the tags optional)

I also tested TCX generation w/ these changes and it works! Thanks!!

Happy to help 🙂 Do you think there may be any more issues with this approach? Can the issue be closed otherwise?

commented

Oh Yes.. yes.. I forgot to close it..

Thanks again!!