BREAKTHROUGH! Using JXA to Extract an Image from KM XML

Using JXA to Extract an Image from KM XML

I've finally figured out how to use JXA to extract Images from a KM XML string.

This has literally taken me years and years to figure out.

First I'll explain the details, then I'll provide some macros.

Preface

I've only just now figured this out. We may discover some enhancements or other tricks after I post this, and I welcome them all!

Details

Images are stored in KM XML (aka "Plist" strings) as base64 data, like this:

<key>Image</key>
<data>
TU0AKgAABliAP+BP8AQWDQeEQmFQuGQ2HQ+I
RGJROKRWLQWBwSLxuOR2PR+QSGDRmRSWTSeU
...
</data>

Up until now, I couldn't figure out how to access that data in JXA. But now I have.

Converting an XML string into a JXA object

For those of you who don't already have code like this:

ObjC.import('AppKit');

var plist = convertXmlToPlist(xml);
if (Array.isArray(plist))
	plist = plist[0];

function convertXmlToPlist(xml) {
	var nsError = $();
	var result = ObjC.deepUnwrap(
		$.NSPropertyListSerialization.propertyListWithDataOptionsFormatError(
			$(xml).dataUsingEncoding($.NSUTF8StringEncoding), 0, 0, nsError));
	if (!result)
		throw Error(getNSErrorMessage(nsError, "Could not convert XML to plist"));
	return result;
}

function getNSErrorMessage(nsError, message) {
	try {
		return `${message}. Error: ${ObjC.unwrap(nsError.localizedDescription)}`;
	} catch (e) {
		return message;
	}
}

The Breakthrough Code: Extracting the Image to disk

const outputFilePath = "/tmp/test.tiff";
const imageData = plist.Image; // the key may be different
if (!imageData)
	throw new Error(`Could not find image data`);

var image = $.NSImage.alloc.initWithData(imageData);
var tiff = image.TIFFRepresentation;
tiff.writeToFileAtomically(outputFilePath, true);

That's really all there is to it.

getNestedProperty

Since the key for the image may be different for each action or group, I created this function, which lets you specify the key like this: "Image" or "Conditions.ConditionList.0.Image"

const value = getNestedProperty(plist, "Conditions.ConditionList.0.Image");

function getNestedProperty(obj, path) {
	const keys = path.split('.');
	let result = obj;
	for (let key of keys) {
		if (result && key in result)
			result = result[key];
		else
			return undefined;
	}
	return result;
}

So we can change the original code to this:

const imageData = getNestedProperty(plist, "Conditions.ConditionList.0.Image");
if (!imageData)
	throw new Error(`Could not find image data`);

Example Macros

Extract XML Image to Disk.v1.0.kmmacros.zip (4.3 KB)


If I made any typo-syntax errors, let me know.

Let me know what you think!

4 Likes