myst-to-jats
Version:
Export from MyST Markdown to JATS
94 lines (93 loc) • 3.17 kB
JavaScript
import { liftChildren, NotebookCell } from 'myst-common';
import { remove } from 'unist-util-remove';
import { selectAll } from 'unist-util-select';
export function sectionAttrsFromBlock(node) {
const output = {};
if (node.kind) {
const blockType = node.kind;
if (Object.values(NotebookCell).includes(blockType)) {
output['sec-type'] = blockType;
}
}
if (node.identifier)
output.id = node.identifier;
return output;
}
function blockIsNotebookCode(node) {
// Markdown blocks will be divided to sections later by headings.
return sectionAttrsFromBlock(node)['sec-type'] === NotebookCell.code;
}
function blockIsNotebookFigure(node) {
return !!node.data?.['fig-cap'];
}
function headingsToSections(tree) {
const stack = [];
const children = [];
function push(child) {
const top = stack[stack.length - 1];
if (top) {
top.children.push(child);
}
else {
children.push(child);
}
}
function newSection(heading) {
const { enumerator, enumerated, ...filtered } = heading;
const next = { ...filtered, type: 'section', children: [] };
while (stack[stack.length - 1] && stack[stack.length - 1].depth >= heading.depth)
stack.pop();
push(next);
stack.push(next);
return { enumerator, enumerated };
}
tree.children?.forEach((child) => {
if (child.type === 'heading') {
const { enumerator, enumerated } = newSection(child);
push({ type: 'heading', enumerator, enumerated, children: child.children });
}
else {
push(child);
}
});
tree.children = children;
}
/**
* This transform does the following:
* - For sub-articles:
* - Blocks are converted to sections so notebook cell divisions are maintained.
* - Within each block, headers are converted to sections.
* - For main articles:
* - Notebook code cell blocks (with meta.type of "notebook-code") are deleted.
* - Remaining blocks are removed, lifting children up a level
* - Top-level heading nodes are then used to break the tree into section nodes,
* with heading and subsequent nodes as children
*/
export function sectionTransform(tree, opts) {
if (opts?.isSubArticle) {
selectAll('block', tree).forEach((node) => {
node.type = 'section';
node.depth = 0;
headingsToSections(node);
});
return;
}
selectAll('block', tree).forEach((node) => {
if (blockIsNotebookFigure(node)) {
node.type = 'section';
}
else if (blockIsNotebookCode(node)) {
node.type = '__delete__';
}
});
const removed = remove(tree, '__delete__');
if (removed === null) {
// remove is unhappy if all children are removed - this forces it through
tree.children = [];
}
liftChildren(tree, 'block'); // this looses part information. TODO: milestones
headingsToSections(tree);
}
export const sectionPlugin = (opts) => (tree) => {
sectionTransform(tree, opts);
};