Skip to content

Commit

Permalink
Fix the skipped test cases in the PreTeXt package (#103)
Browse files Browse the repository at this point in the history
Necessary macro subs and environment subs to create valid PreTeXt from LaTeX.
  • Loading branch information
renee-k authored Aug 15, 2024
1 parent 419b8df commit 27e9fbf
Show file tree
Hide file tree
Showing 15 changed files with 699 additions and 377 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import {
} from "@unified-latex/unified-latex-util-split";
import { visit } from "@unified-latex/unified-latex-util-visit";
import { VFileMessage } from "vfile-message";
import { makeWarningMessage } from "./utils";

/**
* All the divisions, where each item is {division macro, mapped environment}.
* Note that this is ordered from the "largest" division to the "smallest" division.
*/
const divisions: { division: string; mappedEnviron: string }[] = [
export const divisions: { division: string; mappedEnviron: string }[] = [
{ division: "part", mappedEnviron: "_part" },
{ division: "chapter", mappedEnviron: "_chapter" },
{ division: "section", mappedEnviron: "_section" },
Expand All @@ -34,7 +35,7 @@ const isDivisionMacro = match.createMacroMatcher(
);

// check if an environment is a newly created environment
const isMappedEnviron = match.createEnvironmentMatcher(
export const isMappedEnviron = match.createEnvironmentMatcher(
divisions.map((x) => x.mappedEnviron)
);

Expand All @@ -54,29 +55,15 @@ export function breakOnBoundaries(ast: Ast.Ast): { messages: VFileMessage[] } {
return anyMacro(child) && isDivisionMacro(child);
})
) {
const message = new VFileMessage(
"Warning: hoisted out of a group, which might break the LaTeX code."
// add a warning message
messagesLst.messages.push(
makeWarningMessage(
node,
"Warning: hoisted out of a group, which might break the LaTeX code.",
"break-on-boundaries"
)
);

// add the position of the group if available
if (node.position) {
message.line = node.position.start.line;
message.column = node.position.start.column;
message.position = {
start: {
line: node.position.start.line,
column: node.position.start.column,
},
end: {
line: node.position.end.line,
column: node.position.end.column,
},
};
}

message.source = "LatexConversion";
messagesLst.messages.push(message);

return node.content;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import * as Ast from "@unified-latex/unified-latex-types";
import { htmlLike } from "@unified-latex/unified-latex-util-html-like";
import {
parseTabularSpec,
TabularColumn,
} from "@unified-latex/unified-latex-ctan/package/tabularx";
import { parseAlignEnvironment } from "@unified-latex/unified-latex-util-align";
import { getArgsContent } from "@unified-latex/unified-latex-util-arguments";
import { trim } from "@unified-latex/unified-latex-util-trim";

type Attributes = Record<string, string | Record<string, string>>;

/**
* Convert env into a tabular in PreTeXt.
*/
export function createTableFromTabular(env: Ast.Environment) {
const tabularBody = parseAlignEnvironment(env.content);
const args = getArgsContent(env);
let columnSpecs: TabularColumn[] = [];
try {
columnSpecs = parseTabularSpec(args[1] || []);
} catch (e) {}

// for the tabular tag
const attributes: Attributes = {};

// we only need the col tags if one of the columns aren't left aligned/have a border
let notLeftAligned: boolean = false;

// stores which columns have borders to the right
// number is the column's index in columnSpecs
const columnRightBorder: Record<number, boolean> = {};

const tableBody = tabularBody.map((row) => {
const content = row.cells.map((cell, i) => {
const columnSpec = columnSpecs[i];

if (columnSpec) {
const { alignment } = columnSpec;

// this will need to be in the tabular tag
if (
columnSpec.pre_dividers.some(
(div) => div.type === "vert_divider"
)
) {
attributes["left"] = "minor";
}

// check if the column has a right border
if (
columnSpec.post_dividers.some(
(div) => div.type === "vert_divider"
)
) {
columnRightBorder[i] = true;
}

// check if the default alignment isn't used
if (alignment.alignment !== "left") {
notLeftAligned = true;
}
}

// trim whitespace off cell
trim(cell);

return htmlLike({
tag: "cell",
content: cell,
});
});
return htmlLike({ tag: "row", content });
});

// add col tags if needed
if (notLeftAligned || Object.values(columnRightBorder).some((b) => b)) {
// go backwards since adding col tags to the front of the tableBody list
// otherwise, col tags will be in the reversed order
for (let i = columnSpecs.length; i >= 0; i--) {
const columnSpec = columnSpecs[i];

if (!columnSpec) {
continue;
}

const colAttributes: Attributes = {};
const { alignment } = columnSpec;

// add h-align attribute if not default
if (alignment.alignment !== "left") {
colAttributes["halign"] = alignment.alignment;
}

// if there is a right border add it
if (columnRightBorder[i] === true) {
colAttributes["right"] = "minor";
}

tableBody.unshift(
htmlLike({ tag: "col", attributes: colAttributes })
);
}
}

return htmlLike({
tag: "tabular",
content: tableBody,
attributes: attributes,
});
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import cssesc from "cssesc";
import {
parseTabularSpec,
TabularColumn,
} from "@unified-latex/unified-latex-ctan/package/tabularx";
import { htmlLike } from "@unified-latex/unified-latex-util-html-like";
import * as Ast from "@unified-latex/unified-latex-types";
import { parseAlignEnvironment } from "@unified-latex/unified-latex-util-align";
import {
getArgsContent,
getNamedArgsContent,
} from "@unified-latex/unified-latex-util-arguments";
import { getNamedArgsContent } from "@unified-latex/unified-latex-util-arguments";
import { match } from "@unified-latex/unified-latex-util-match";
import { printRaw } from "@unified-latex/unified-latex-util-print-raw";
import { wrapPars } from "../wrap-pars";
import { VisitInfo } from "@unified-latex/unified-latex-util-visit";
import { VFile } from "vfile";
import { makeWarningMessage } from "./utils";
import { createTableFromTabular } from "./create-table-from-tabular";

const ITEM_ARG_NAMES_REG = ["label"] as const;
const ITEM_ARG_NAMES_BEAMER = [null, "label", null] as const;
Expand Down Expand Up @@ -48,139 +41,92 @@ function getItemArgs(node: Ast.Macro): ItemArgs {
return ret as ItemArgs;
}

function enumerateFactory(parentTag = "ol", className = "enumerate") {
function enumerateFactory(parentTag = "ol") {
return function enumerateToHtml(env: Ast.Environment) {
// The body of an enumerate has already been processed and all relevant parts have
// been attached to \item macros as arguments.
const items = env.content.filter((node) => match.macro(node, "item"));

// Figure out if there any manually-specified item labels. If there are,
// we need to add a title tag
let isDescriptionList = false;

const content = items.flatMap((node) => {
if (!match.macro(node) || !node.args) {
return [];
}

const attributes: Record<string, string | Record<string, string>> =
{};
// Figure out if there any manually-specified item labels. If there are,
// we need to specify a custom list-style-type.
// We test the open mark to see if an optional argument was actually supplied.
const namedArgs = getItemArgs(node);

// if there are custom markers, don't want the title tag to be wrapped in pars
// so we wrap the body first
namedArgs.body = wrapPars(namedArgs.body);

// check if a custom marker is used
if (namedArgs.label != null) {
const formattedLabel = cssesc(printRaw(namedArgs.label || []));
attributes.style = {
// Note the space after `formattedLabel`. That is on purpose!
"list-style-type": formattedLabel
? `'${formattedLabel} '`
: "none",
};
isDescriptionList = true;

// add title tag containing custom marker
namedArgs.body.unshift(
htmlLike({
tag: "title",
content: namedArgs.label,
})
);
}

const body = namedArgs.body;

return htmlLike({
tag: "li",
content: wrapPars(body),
attributes,
content: body,
});
});

return htmlLike({
tag: parentTag,
attributes: { className },
tag: isDescriptionList ? "dl" : parentTag,
content,
});
};
}

function createCenteredElement(env: Ast.Environment) {
return htmlLike({
tag: "center",
attributes: { className: "center" },
content: env.content,
});
}

function createTableFromTabular(env: Ast.Environment) {
const tabularBody = parseAlignEnvironment(env.content);
const args = getArgsContent(env);
let columnSpecs: TabularColumn[] = [];
try {
columnSpecs = parseTabularSpec(args[1] || []);
} catch (e) {}

const tableBody = tabularBody.map((row) => {
const content = row.cells.map((cell, i) => {
const columnSpec = columnSpecs[i];
const styles: Record<string, string> = {};
if (columnSpec) {
const { alignment } = columnSpec;
if (alignment.alignment === "center") {
styles["text-align"] = "center";
}
if (alignment.alignment === "right") {
styles["text-align"] = "right";
}
if (
columnSpec.pre_dividers.some(
(div) => div.type === "vert_divider"
)
) {
styles["border-left"] = "1px solid";
}
if (
columnSpec.post_dividers.some(
(div) => div.type === "vert_divider"
)
) {
styles["border-right"] = "1px solid";
}
}
return htmlLike(
Object.keys(styles).length > 0
? {
tag: "td",
content: cell,
attributes: { style: styles },
}
: {
tag: "td",
content: cell,
}
);
});
return htmlLike({ tag: "tr", content });
});
/**
* Remove the env environment by returning the content in env only.
*/
function removeEnv(env: Ast.Environment, info: VisitInfo, file?: VFile) {
// add warning
file?.message(
makeWarningMessage(
env,
`Warning: There is no equivalent tag for \"${env.env}\", so the ${env.env} environment was removed.`,
"environment-subs"
)
);

return htmlLike({
tag: "table",
content: [
htmlLike({
tag: "tbody",
content: tableBody,
}),
],
attributes: { className: "tabular" },
});
return env.content;
}

/**
* Rules for replacing a macro with an html-like macro
* that will render has html when printed.
* that will render has pretext when printed.
*/
export const environmentReplacements: Record<
string,
(
node: Ast.Environment,
info: VisitInfo
) => Ast.Macro | Ast.String | Ast.Environment
info: VisitInfo,
file?: VFile
) => Ast.Macro | Ast.String | Ast.Environment | Ast.Node[]
> = {
enumerate: enumerateFactory("ol"),
itemize: enumerateFactory("ul", "itemize"),
center: createCenteredElement,
itemize: enumerateFactory("ul"),
center: removeEnv,
tabular: createTableFromTabular,
quote: (env) => {
return htmlLike({
tag: "blockquote",
content: env.content,
attributes: { className: "environment quote" },
});
},
};
Loading

0 comments on commit 27e9fbf

Please sign in to comment.