Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
mjunkmyjunk opened this issue Sep 3, 2021 · 13 comments
Closed

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

mjunkmyjunk opened this issue Sep 3, 2021 · 13 comments

Comments

@mjunkmyjunk
Copy link

mjunkmyjunk commented Sep 3, 2021

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))
@mjunkmyjunk mjunkmyjunk changed the title how to "skip how to "skip" certain XML tags / element in a TCX file Sep 3, 2021
@MaxDesiatov
Copy link
Collaborator

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?

@mjunkmyjunk
Copy link
Author

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"
    }

@MaxDesiatov
Copy link
Collaborator

MaxDesiatov commented Sep 3, 2021

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

@mjunkmyjunk
Copy link
Author

Thanks for the link.

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

@MaxDesiatov
Copy link
Collaborator

Did you try making their corresponding properties optional?

@MaxDesiatov
Copy link
Collaborator

MaxDesiatov commented Sep 3, 2021

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.

@mjunkmyjunk
Copy link
Author

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.

@MaxDesiatov
Copy link
Collaborator

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.

@mjunkmyjunk
Copy link
Author

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. 🙏

@MaxDesiatov
Copy link
Collaborator

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.

@mjunkmyjunk
Copy link
Author

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!!

@MaxDesiatov
Copy link
Collaborator

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

@mjunkmyjunk
Copy link
Author

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

Thanks again!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants