Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Break Up LaTeX Source on Section Macros #101

Merged
merged 43 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
3a85c98
added math mode macro report function and test file
renee-k May 25, 2024
0c1971a
updated report unsupported katex and expand command files
renee-k May 26, 2024
a140ea3
added more tests to be implemented
renee-k May 26, 2024
575141d
implemented expand macros tests and function
renee-k May 28, 2024
463f366
added files for breaking source on boundaries
renee-k May 29, 2024
26c09ac
added some test cases
renee-k May 29, 2024
2e5fdbd
updated code
renee-k May 29, 2024
2711765
formatted code
renee-k May 29, 2024
ac23334
Revert "added some test cases"
renee-k May 29, 2024
30bac19
Revert "updated code"
renee-k May 29, 2024
292de9f
fixed test file
renee-k May 29, 2024
a450ac1
addressed some katex related comments
renee-k May 30, 2024
bd2f4d5
added more tests
renee-k May 31, 2024
3f08772
started implementing break-on-boundaries
renee-k Jun 2, 2024
3e1a7e3
successfully splits on parts
renee-k Jun 4, 2024
76352c5
removed uneeded variable
renee-k Jun 4, 2024
d2bc84f
implemented for half the divisions
renee-k Jun 5, 2024
6f6221a
revised tests
renee-k Jun 5, 2024
b55e08c
revised some test descriptions
renee-k Jun 5, 2024
fa392d0
made tests more readable
renee-k Jun 5, 2024
49e25bc
fixed expand userdefined macros
renee-k Jun 5, 2024
577d1be
addressed more PR comments
renee-k Jun 8, 2024
7648bc7
moved files to pre-conversion-subs
renee-k Jun 9, 2024
bdd9581
added support for groups
renee-k Jun 11, 2024
422fd59
added more test cases
renee-k Jun 12, 2024
ffeb18f
Merge branch 'pretext' into break-section-pretext
renee-k Jun 12, 2024
34214da
Add pretext to tested branches
siefkenj Jun 12, 2024
9631ed4
moved files
renee-k Jun 12, 2024
8c4ed53
Merge branch 'main' into break-section-pretext
renee-k Jun 12, 2024
1f483a4
resolved some PR comments
renee-k Jun 15, 2024
8d4d570
added more test cases
renee-k Jun 17, 2024
ae7d000
restructured code
renee-k Jun 18, 2024
c5ed819
added warning messages
renee-k Jun 19, 2024
fc8511c
addressed some comments
renee-k Jun 26, 2024
9379b15
Merge branch 'pretext' into break-section-pretext
renee-k Jun 26, 2024
35627d8
used a custom macro matcher
renee-k Jun 26, 2024
7fba2d7
self-documented divisions
renee-k Jun 26, 2024
9f407ca
updated documentation
renee-k Jun 28, 2024
3d43101
fixed all test cases
renee-k Jul 4, 2024
1a586ec
made custom environment matcher
renee-k Jul 6, 2024
51ba97e
removed groups properly
renee-k Jul 9, 2024
5e388ae
imported VFileMessage
renee-k Jul 12, 2024
5b467b9
finished VFileMessage implementation
renee-k Jul 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { env, arg } from "@unified-latex/unified-latex-builder";
import * as Ast from "@unified-latex/unified-latex-types";
import { getNamedArgsContent } from "@unified-latex/unified-latex-util-arguments";
import {
anyEnvironment,
anyMacro,
match,
} from "@unified-latex/unified-latex-util-match";
import { replaceNode } from "@unified-latex/unified-latex-util-replace";
import {
splitOnMacro,
unsplitOnMacro,
} from "@unified-latex/unified-latex-util-split";
import { visit } from "@unified-latex/unified-latex-util-visit";
import { VFileMessage } from "vfile-message";

/**
* 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 }[] = [
{ division: "part", mappedEnviron: "_part" },
{ division: "chapter", mappedEnviron: "_chapter" },
{ division: "section", mappedEnviron: "_section" },
{ division: "subsection", mappedEnviron: "_subsection" },
{ division: "subsubsection", mappedEnviron: "_subsubsection" },
{ division: "paragraph", mappedEnviron: "_paragraph" },
{ division: "subparagraph", mappedEnviron: "_subparagraph" },
];

// check if a macro is a division macro
const isDivisionMacro = match.createMacroMatcher(
divisions.map((x) => x.division)
);

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

/**
* Breaks up division macros into environments. Returns an object of warning messages
* for any groups that were removed.
*/
export function breakOnBoundaries(ast: Ast.Ast): { messages: VFileMessage[] } {
// messages for any groups removed
const messagesLst: { messages: VFileMessage[] } = { messages: [] };

replaceNode(ast, (node) => {
if (match.group(node)) {
// remove if it contains a division as an immediate child
if (
node.content.some((child) => {
return anyMacro(child) && isDivisionMacro(child);
})
) {
const message = new VFileMessage(
"Warning: hoisted out of a group, which might break the LaTeX code."
);

// 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;
}
}
});

visit(ast, (node, info) => {
// needs to be an environment, root, or group node
if (
!(
anyEnvironment(node) ||
node.type === "root" ||
match.group(node)
) ||
// skip math mode
info.context.hasMathModeAncestor
) {
return;
}
// if it's an environment, make sure it isn't a newly created one
else if (anyEnvironment(node) && isMappedEnviron(node)) {
return;
}

// now break up the divisions, starting at part
node.content = breakUp(node.content, 0);
});

replaceNode(ast, (node) => {
// remove all old division nodes
if (anyMacro(node) && isDivisionMacro(node)) {
return null;
}
});

return messagesLst;
}

/**
* Recursively breaks up the AST at the division macros.
*/
function breakUp(content: Ast.Node[], depth: number): Ast.Node[] {
// broke up all divisions
if (depth > 6) {
return content;
}

const splits = splitOnMacro(content, divisions[depth].division);

// go through each segment to recursively break
for (let i = 0; i < splits.segments.length; i++) {
splits.segments[i] = breakUp(splits.segments[i], depth + 1);
}

createEnvironments(splits, divisions[depth].mappedEnviron);

// rebuild this part of the AST
return unsplitOnMacro(splits);
}

/**
* Create the new environments that replace the division macros.
*/
function createEnvironments(
splits: { segments: Ast.Node[][]; macros: Ast.Macro[] },
newEnviron: string
): void {
// loop through segments (skipping first segment)
for (let i = 1; i < splits.segments.length; i++) {
// get the title
const title = getNamedArgsContent(splits.macros[i - 1])["title"];
const titleArg: Ast.Argument[] = [];

// create title argument
if (title) {
titleArg.push(arg(title, { braces: "[]" }));
}

// wrap segment with a new environment
splits.segments[i] = [env(newEnviron, splits.segments[i], titleArg)];
}
}
159 changes: 159 additions & 0 deletions packages/unified-latex-to-pretext/tests/break-on-boundaries.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { describe, it, expect } from "vitest";
import util from "util";
import { getParser } from "@unified-latex/unified-latex-util-parse";
import { printRaw } from "@unified-latex/unified-latex-util-print-raw";
import { breakOnBoundaries } from "../libs/pre-conversion-subs/break-on-boundaries";

// Make console.log pretty-print by default
const origLog = console.log;
console.log = (...args) => {
origLog(...args.map((x) => util.inspect(x, false, 10, true)));
};

describe("unified-latex-to-pretext:break-on-boundaries", () => {
let value: string;

it("can break on parts", () => {
value = String.raw`\part{Foo}Hi, this is a part\part{Bar}This is another part`;

const parser = getParser();
const ast = parser.parse(value);

expect(breakOnBoundaries(ast)).toEqual({ messages: [] });

expect(printRaw(ast)).toEqual(
String.raw`\begin{_part}[Foo]Hi, this is a part\end{_part}\begin{_part}[Bar]This is another part\end{_part}`
);
});

it("can break on a combination of divisions", () => {
value = String.raw`\part{part1}\section{Section1}Hi, this is a section\chapter{chap1}This is a chapter\section{Subsection2}`;

const parser = getParser();
const ast = parser.parse(value);

expect(breakOnBoundaries(ast)).toEqual({ messages: [] });

expect(printRaw(ast)).toEqual(
renee-k marked this conversation as resolved.
Show resolved Hide resolved
"" +
String.raw`\begin{_part}[part1]` +
String.raw`\begin{_section}[Section1]Hi, this is a section\end{_section}` +
String.raw`\begin{_chapter}[chap1]This is a chapter` +
String.raw`\begin{_section}[Subsection2]\end{_section}\end{_chapter}\end{_part}`
);
});

it("can break on divisions wrapped around by a document environment", () => {
value = String.raw`\begin{document}\section{Baz}Hi, this is a subsection\subsubsection{Foo}description.\end{document}`;

const parser = getParser();
const ast = parser.parse(value);

expect(breakOnBoundaries(ast)).toEqual({ messages: [] });

expect(printRaw(ast)).toEqual(
String.raw`\begin{document}\begin{_section}[Baz]Hi, this is a subsection` +
String.raw`\begin{_subsubsection}[Foo]description.\end{_subsubsection}` +
String.raw`\end{_section}\end{document}`
);
});

it("can break on divisions wrapped around by different environments", () => {
value =
String.raw`\begin{center}\part{name}Hi, this is a part\begin{environ}` +
String.raw`\subparagraph{title}description.\end{environ}\end{center}`;

const parser = getParser();
const ast = parser.parse(value);

expect(breakOnBoundaries(ast).messages.length).toEqual(0);

expect(printRaw(ast)).toEqual(
String.raw`\begin{center}\begin{_part}[name]Hi, this is a part` +
String.raw`\begin{environ}\begin{_subparagraph}[title]description.` +
String.raw`\end{_subparagraph}\end{environ}\end{_part}\end{center}`
);
});

it("can break on divisions in a group", () => {
value =
String.raw`\begin{document}\chapter{Chap}` +
String.raw`{\paragraph{Intro}Introduction.\begin{center}\subparagraph{Conclusion}Conclusion.\end{center}}` +
String.raw`Chapter finished.\end{document}`;

const parser = getParser();
const ast = parser.parse(value);

expect(breakOnBoundaries(ast).messages.length).toEqual(1);

expect(printRaw(ast)).toEqual(
String.raw`\begin{document}\begin{_chapter}[Chap]\begin{_paragraph}[Intro]Introduction.` +
String.raw`\begin{center}\begin{_subparagraph}[Conclusion]Conclusion.\end{_subparagraph}` +
String.raw`\end{center}Chapter finished.\end{_paragraph}\end{_chapter}\end{document}`
);
});

it("can break on divisions in nested groups", () => {
value =
String.raw`\part{part1}{\subsection{Intro}description.` +
String.raw`\subsubsection{body}more text.{\subparagraph{Conclusion}Conclusion.}}`;

const parser = getParser();
const ast = parser.parse(value);

expect(breakOnBoundaries(ast).messages.length).toEqual(2);

expect(printRaw(ast)).toEqual(
String.raw`\begin{_part}[part1]\begin{_subsection}[Intro]description.` +
String.raw`\begin{_subsubsection}[body]more text.\begin{_subparagraph}[Conclusion]Conclusion.` +
String.raw`\end{_subparagraph}\end{_subsubsection}\end{_subsection}\end{_part}`
);
});

it("doesn't break on groups without a division as an immediate child", () => {
value =
String.raw`\part{part1}{\subsection{Intro}` +
String.raw`\subsubsection{body}{$\mathbb{N}$\subparagraph{Conclusion}{no divisions 1}Conclusion.}}{no divisions 2}`;

const parser = getParser();
const ast = parser.parse(value);

expect(breakOnBoundaries(ast).messages.length).toEqual(2);

expect(printRaw(ast)).toEqual(
String.raw`\begin{_part}[part1]\begin{_subsection}[Intro]\begin{_subsubsection}[body]` +
String.raw`$\mathbb{N}$\begin{_subparagraph}[Conclusion]{no divisions 1}Conclusion.{no divisions 2}` +
String.raw`\end{_subparagraph}\end{_subsubsection}\end{_subsection}\end{_part}`
);
});

it("can break on divisions with latex in their titles", () => {
renee-k marked this conversation as resolved.
Show resolved Hide resolved
value = String.raw`\chapter{$x = \frac{1}{2}$}Chapter 1\subsection{\"name\_1\" \$}This is subsection`;

const parser = getParser();
const ast = parser.parse(value);

expect(breakOnBoundaries(ast).messages.length).toEqual(0);

expect(printRaw(ast)).toEqual(
String.raw`\begin{_chapter}[$x = \frac{1}{2}$]Chapter 1` +
String.raw`\begin{_subsection}[\"name\_1\" \$]This is subsection` +
String.raw`\end{_subsection}\end{_chapter}`
);
});

it("can break on divisions and trim whitespace around division beginnings and endings", () => {
value = String.raw` \subsubsection{first}subsection 1 \paragraph{body}This is paragraph `;

const parser = getParser();
const ast = parser.parse(value);

expect(breakOnBoundaries(ast).messages.length).toEqual(0);

expect(printRaw(ast)).toEqual(
String.raw`\begin{_subsubsection}[first]subsection 1 ` +
String.raw`\begin{_paragraph}[body]This is paragraph` +
String.raw`\end{_paragraph}\end{_subsubsection}`
);
});
});
siefkenj marked this conversation as resolved.
Show resolved Hide resolved