JXA: Problem XML Serializing a Plist object (Macro) that contains custom data

@ComplexPoint - Rob, I've put together an example that shows an issue I'm dealing with, and I hope you can figure out what to do about it.

Here's the very simple, pared-down and documented example:

XML Serialization Problem.kmmacros (28.9 KB)

The issue is, if I have a macro plist object that contains CustomIconData (or other custom data) and I try to serialize it to XML, the CustomIconData goes away. Obviously this shouldn't happen.

This has to be an issue with how I'm using it in JXA, because it obviously can be XML serialized, or else how would @peternlewis be able to put it on the clipboard in the first place?

@peternlewis - What ObjectiveC functions do you use to put a macro on the clipboard, when someone types ⌘C? I think I'm missing a step that does something to the CustomIconData (and other custom data), so the NSMutableData can be serialized to XML.

Thanks.

Unlikely to look closely at this for a few days now, but generally, if in doubt, I tend to explore things through NSXMLDocument:

Thanks, but the problem is with creating the XML via serialization , not in parsing it.

Haven’t looked, but are you base64 encoding it to a string yourself ?

No. When Peter puts it on the clipboard, the entire plist is a string, and the CustomIconData node looks like this:

<key>CustomIconData</key>
<data>
TU0AKgAAEyyAP+BP8AQWDQeEQmFQt+v9+AB5P11gBzPdogBtPJlABlOtVgBv
PFqAB7vuCgGFymVSuWS2XS+YSuCQUEgSCiIICwADcKlYACUHDcABoEi0AA4B
... removed for brevity ...
AAAoGgAAFZ8AALg2Y3VydgAAAAAAAAABAc0AAHNmMzIAAAAAAAEMQgAABd7/
//MmAAAHkgAA/ZH///ui///9owAAA9wAAMBs
</data>

When I use the ObjC function to deserialize this to a plist object, and I examine CustomIconData, it says its type is “NSMutableData”. This leads me to believe it contains valid data, although I wouldn’t know how to check for sure.

But when I use the ObjC function to serialize the plist object to XML, I get this:

<key>CustomIconData</key>
<dict/>

This makes me think I’m missing a step, or an option, or something, that tells the serializer how to handle this kind of data.

I think I would tend to convert the NSMutableData to a base64 string – there seem to be various ObjC methods in that zone

NSData base64EncodedDataWithOptions

etc.

NSData base64EncodedDataWithOptions

Thanks. That sounds like a possibility. I can't figure out the exact JXA syntax to use this function with a JXA object. Any ideas?

I’m pretty sure raw data is a native plist type. Stored in the plist, it is either base64 in XML or whatever it does in binary plists.

The CustomIconData is either a string with some KM-specific codes in it, or data with a TIFFRepresentation of the image.

It is. I was just hoping I was missing a step somewhere.

Thanks.

If anyone is interested, I have a workaround for most of my use cases for this issue.

In a nutshell, before I deserialize the XML into a plist object, I do a regex search-and-replace, changing <data> nodes to <string> nodes, and include a specific UUID at the start and end of the string.

After I serialize the plist back to an XML string, I reverse the process, changing the <string> nodes that are marked with my UUID back to <data> nodes.

Of course, this means if I need to access the actual data in the plist objects, I have to take this into account, but as of yet, that hasn’t been an issue. Even if I copy the CustomIconData from one XML plist and paste it into another, it works fine.

This process won’t work when reading binary plist files, like how KM stores its macros.

But for how I generally use KM plists, this suffices.

If anyone wants, I’ll post the code. The comments take up way more lines than the additional code itself.

If anyone’s interested, I finally managed to solve this issue. It took a lot of work, and the details are extremely technical.

I’ve created a JXA library that can do the following, even if there’s “data” node types like CustomIconData:

  1. Deserialize Plist XML into JXA objects.
  2. Serialize JXA objects to Plist XML.
  3. Convert Plist XML to JSON.
  4. Convert JSON to Plist XML.
  5. I can deserialize the KM Macros Plist file, which is in binary format, into JXA objects.

Bottom line is, I can now work with all forms of KM Macros and Actions, in JXA. And get the results back into KM with no loss of any values.

I say this with a fair degree of certainty (Murphy’s Law notwithstanding), because my library routines have methods that verify things work as expected.

For instance, when I convert a Plist XML string to a JXA object, I convert it back to XML to verify that the results are exactly the same. So if anything should ever NOT convert correctly, I’ll know right away.

But I don’t see this happening, as I can convert my entire KM Macros library both directions and they match exactly. Byte-to-byte.

##Technical Summary:

###The Problem:
Plists support a datatype called “Data”, which contains base64-encoded data. KM uses this datatype to store things like CustomIconData and StyledText.

When Plists with this type are deserialized into a JXA object, then serialized back to a Plist XML string, the “Data” values are lost.

There’s appears to be no solution for this, using the tools and APIs that are provided by Apple. I don’t say this lightly - believe me, I’ve done my research. Although if someone can prove me wrong, please do!

###The Solution:

Before I deserialize a Plist XML string to a JXA object, I use some regex magic to “tokenize” Data nodes into String nodes.

After I serialize JXA objects back to Plist XML, I reverse the process, using some more regex magic to convert the tokenized String nodes back to Data nodes.

And any time I use the JSON format, it always originates from the XML format, so the Data nodes are still tokenized, so JSON has no problems with them. Conversion back to XML format de-tokenizes the Data nodes.

As for the tokens themselves, I decided to use UUIDs. I can’t see any situation where this will ever fail, especially since the tokens have the be at the very start and end of the tokenized string, so the likelyhood of this ever happening is next to nil.

I can even work with the Plists for the macros that contain the JXA code that has these UUIDs hard-coded, and they tokenize just fine, because the tokens are never at the start and end of the nodes.

Kudos to anyone who read all the way to this point. Extra bonus points for those people who actually understand what I’m talking about. All one of you. :slight_smile:

2 Likes

Nice work !

1 Like