@ComplexPoint - I'm hoping you, or someone else can help.
I asked ChatGPT for this: "jxa plist save rtf data to file", and it gave me the following script:
ObjC.import('Foundation');
// Function to read plist file and extract RTF data
function getRTFDataFromPlist(plistPath) {
// Load the plist file
var plistData = $.NSData.dataWithContentsOfFile(plistPath);
if (!plistData) {
throw new Error("Failed to load plist data from file.");
}
// Deserialize the plist data into an object
var plist = $.NSPropertyListSerialization.propertyListWithDataOptionsFormatError(plistData, 0, null, null);
if (!plist) {
throw new Error("Failed to deserialize plist data.");
}
// Extract RTF data (assuming it's stored in a key named "RTFKey")
var rtfData = plist.objectForKey('RTFKey');
if (!rtfData) {
throw new Error("No RTF data found in plist.");
}
return rtfData;
}
// Function to save RTF data to a file
function saveRTFToFile(rtfData, outputPath) {
var success = rtfData.writeToFileAtomically(outputPath, true);
if (!success) {
throw new Error("Failed to save RTF data to file.");
}
}
// Example usage
var plistPath = "/path/to/your/file.plist"; // Change this to your plist file path
var outputPath = "/path/to/your/output.rtf"; // Change this to your desired output file path
try {
var rtfData = getRTFDataFromPlist(plistPath);
saveRTFToFile(rtfData, outputPath);
console.log("RTF data successfully saved to file.");
} catch (e) {
console.log("Error: " + e.message);
}
The problem is this line:
plist.objectForKey('RTFKey');
...
plist.objectForKey is not a function.
Can you show us, or point us to, a sample of the kind of plist file which you are expecting to parse to an Object, finding "RTFKey" as a top-level key ?
Presumably the plist you are looking at is not structured as an Array at the top level ?
PS, if it is a serialization of a Dictionary rather than an Array, then my habit would be something like we do with the KM macros plist:
// jsoFromPlistPathLR :: FilePath ->
// Either String Dict
const jsoFromPlistPathLR = fp => {
const
nsDict = $.NSDictionary.dictionaryWithContentsOfURL(
$.NSURL.fileURLWithPath(fp)
);
return nsDict.isNil()
? Left(`Could not be read as .plist: "${fp}"`)
: Right(ObjC.deepUnwrap(nsDict));
};
as in:
Expand disclosure triangle to view JS source
(() => {
"use strict";
ObjC.import("AppKit");
const main = () =>
either(
message => message
)(
dict => JSON.stringify(dict, null, 2)
)(
jsoFromPlistPathLR(
kmPlistPath()
)
);
// --------------------- JXA ---------------------
// applicationSupportPath :: () -> String
const applicationSupportPath = () => {
const uw = ObjC.unwrap;
return uw(
uw($.NSFileManager.defaultManager
.URLsForDirectoryInDomains(
$.NSApplicationSupportDirectory,
$.NSUserDomainMask
)
)[0].path
);
};
// jsoFromPlistPathLR :: FilePath ->
// Either String Dict
const jsoFromPlistPathLR = fp => {
const
nsDict = $.NSDictionary.dictionaryWithContentsOfURL(
$.NSURL.fileURLWithPath(fp)
);
return nsDict.isNil()
? Left(`Could not be read as .plist: "${fp}"`)
: Right(ObjC.deepUnwrap(nsDict));
};
// kmPlistPath :: () -> IO FilePath
const kmPlistPath = () => {
const
kmMacros = [
"/Keyboard Maestro/",
"Keyboard Maestro Macros.plist"
].join("");
return `${applicationSupportPath()}${kmMacros}`;
};
// --------------------- GENERIC ---------------------
// Left :: a -> Either a b
const Left = x => ({
type: "Either",
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: "Either",
Right: x
});
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
// Application of the function fl to the
// contents of any Left value in e, or
// the application of fr to its Right value.
fr => e => "Left" in e
? fl(e.Left)
: fr(e.Right);
return main();
})();
As usual, you've shown me where I'm being forehead-slapping foolish. With the XML now set to a single, <dict> instead of an array, and with some changes I'll discuss below, this works:
Click to expand
(() => {
ObjC.import('Foundation');
// Function to read plist file and extract RTF data
function getRTFDataFromPlist(plistPath, rtfKey) {
// Load the plist file
var plistData = $.NSData.dataWithContentsOfFile(plistPath);
if (!plistData) {
throw new Error("Failed to load plist data from file.");
}
// Deserialize the plist data into an object
var plist = $.NSPropertyListSerialization.propertyListWithDataOptionsFormatError(plistData, 0, null, null);
if (!plist) {
throw new Error("Failed to deserialize plist data.");
}
// Extract RTF data (assuming it's stored in a key named "RTFKey")
var rtfData = plist.objectForKey(rtfKey);
if (!rtfData) {
throw new Error("No RTF data found in plist.");
}
return rtfData;
}
// Function to save RTF data to a file
function saveRTFToFile(rtfData, outputPath) {
var success = rtfData.writeToFileAtomically(outputPath, true);
if (!success) {
throw new Error("Failed to save RTF data to file.");
}
}
function fixRTFFile(filePath) {
const getNSErrorMessage = (nsError, message) => {
try {
return message + ". Error: " + ObjC.unwrap(nsError.localizedDescription);
} catch (e) {
return message;
}
}
const readTextFile = path => {
var nsError = $();
var result = ObjC.unwrap($.NSString.stringWithContentsOfFileEncodingError(
$(path).stringByStandardizingPath, $.NSMacOSRomanStringEncoding, nsError));
if (result == null)
throw Error(getNSErrorMessage(nsError, `File not found or couldn't be read: "${path}"`));
return result;
}
const writeTextFile = (text, path) => {
var nsError = $();
var str = $.NSString.alloc.initWithUTF8String(text);
var result = str.writeToFileAtomicallyEncodingError(
$(path).stringByStandardizingPath, true, $.NSMacOSRomanStringEncoding, nsError);
if (!result)
throw Error(getNSErrorMessage(nsError, `Could not write file: "${path}"`));
}
const text = readTextFile(filePath)
.replace(/(^.*?)(?={\\rtf1)/, "")
.replace(/(?:})([^}]*?$)/, "}");
writeTextFile(text, filePath);
}
// Example usage
var plistPath = "/Users/Dan/Documents/Development/Keyboard Maestro Offload/_testFiles/Comment.xml";
var outputPath = "/Users/Dan/Documents/Development/Keyboard Maestro Offload/_testFiles/Comment.rtf";
try {
var rtfData = getRTFDataFromPlist(plistPath, "StyledText");
saveRTFToFile(rtfData, outputPath);
fixRTFFile(outputPath)
console.log("RTF data successfully saved to file.");
} catch (e) {
console.log("Error: " + e.message);
}
})();
As for changes, I passed the key into the routine. But the big change was adding this at the end:
fixRTFFile(outputPath)
...and the corresponding function.
It's to remove the "cruft" at the start and end of the file:
Ideally I'd like to remove the cruft before saving it to disk, but I can't figure out how to convert rtfData to a JS string I can manipulate, then change it back to the correct format and save it to disk.
So I have something that appears to work (big thanks for rattling my brain), but it would be cool if I could fix the cruft before saving it out to disk in the first place.
Awaiting your brilliance... ;p
PS: Regarding your other comment, let's talk about that after we finish talking about this.