Got it.
Could you give me:
- a snippet of TaskPaper (as a file attachment here, perhaps zipped) which provokes the problem
- a quick description of an import procedure (for Scrivener, for example, or iThought), which will reproduce the issue
?
In the meanwhile, it may be worth experimenting with this variant, which just post-processes the TP3 ItemSerializer OPML with .replace(/&/gu, "&")
Expand disclosure triangle to view JS source
(() => {
"use strict";
// RobTrew @2023
// Possible entity fix
// Ver 0.01
// ---------- TASKPAPER EVALUATION CONTEXT -----------
const tp3Context = editor =>
ItemSerializer.serializeItems(
editor.outline.items,
editor,
ItemSerializer.OPMLMimeType
);
// ------------- JXA EVALUATION CONTEXT --------------
// main :: IO ()
const main = () => {
const
doc = Application("TaskPaper")
.documents.at(0);
return either(
alert("Save TaskPaper as OPML")
)(
fpWritten => fpWritten
)(
doc.exists() ? (() => {
const maybeFile = doc.file();
return bindLR(
null !== maybeFile ? (
Right(`${maybeFile}`)
) : Left("First save TaskPaper document.")
)(fpTaskPaper => {
const
fpFolder = takeDirectory(fpTaskPaper),
fileStem = takeBaseName(fpTaskPaper),
fpOut = Object.assign(
Application.currentApplication(),
{includeStandardAdditions: true}
)
.chooseFileName({
withPrompt: "Save as OPML",
defaultName: `${fileStem}.opml`,
defaultLocation: fpFolder
});
return writeFileLR(`${fpOut}`)(
doc.evaluate({
script: `${tp3Context}`
})
.replace(/&/gu, "&")
);
});
})() : Left("No document open in TaskPaper.")
);
};
// ----------------------- JXA -----------------------
// alert :: String => String -> IO String
const alert = title =>
s => {
const sa = Object.assign(
Application("System Events"), {
includeStandardAdditions: true
});
return (
sa.activate(),
sa.displayDialog(s, {
withTitle: title,
buttons: ["OK"],
defaultButton: "OK"
}),
s
);
};
// --------------------- 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
});
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = m =>
mf => m.Left ? (
m
) : mf(m.Right);
// 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 => e.Left ? (
fl(e.Left)
) : fr(e.Right);
// takeBaseName :: FilePath -> String
const takeBaseName = fp =>
// The filename without any extension.
("" !== fp) ? (
("/" !== fp[fp.length - 1]) ? (() => {
const fn = fp.split("/").slice(-1)[0];
return fn.includes(".") ? (
fn.split(".").slice(0, -1)
.join(".")
) : fn;
})() : ""
) : "";
// takeDirectory :: FilePath -> FilePath
const takeDirectory = fp =>
// The directory component of a filepath.
"" !== fp ? (
(xs => xs.length > 0 ? xs.join("/") : ".")(
fp.split("/").slice(0, -1)
)
) : ".";
// writeFileLR :: FilePath ->
// String -> Either String IO FilePath
const writeFileLR = fp =>
// Either a message or the filepath
// to which the string has been written.
s => {
const
uw = ObjC.unwrap,
e = $(),
efp = $(fp).stringByStandardizingPath;
return $.NSString.alloc.initWithUTF8String(s)
.writeToFileAtomicallyEncodingError(
efp, false,
$.NSUTF8StringEncoding, e
) ? (
Right(uw(efp))
) : Left(uw(e.localizedDescription));
};
// MAIN ---
return main();
})();