-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add examples * Test examples
- Loading branch information
Showing
12 changed files
with
634 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Examples | ||
|
||
Here you can find annotated examples of how to use `unified-latex` to accomplish common tasks. The examples can be run | ||
with `npx vite-node <example file>`. | ||
|
||
- `count-macros.ts` - goes through the basics of parsing source to a `unified-latex` AST and walking the tree to gather | ||
information about its contents. | ||
- `custom-macros.ts` - shows how to add your own macros to the parse process. | ||
- `ignore-defaults.ts` - shows how to parse a string without including any default packages (not even LaTeX2e standard ones). | ||
- `expanding-or-replacing-macros.ts` - shows how to expand or replace macros present in an AST. | ||
- `using-unified.ts` - shows how to use `unified` in combination with `unified-latex` to build a processing pipeline. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/** | ||
* This example shows how to count macros in a tex string and print out statistics. | ||
*/ | ||
import { getParser } from "@unified-latex/unified-latex-util-parse"; | ||
import { visit } from "@unified-latex/unified-latex-util-visit"; | ||
import { anyMacro } from "@unified-latex/unified-latex-util-match"; | ||
import { printRaw } from "@unified-latex/unified-latex-util-print-raw"; | ||
|
||
const TEX_SOURCE = String.raw` | ||
This is \textbf{an} example of a \LaTeX{} document with \textit{some} macros. | ||
\[ | ||
e^x = \sum_{n=0}^{\infty} \frac{x^n}{n!}. | ||
\] | ||
What an \textit{\textbf{amazing}} formula! | ||
`; | ||
|
||
// The quickest way to get started is to create a parser with `getParser`. | ||
const parser = getParser(); | ||
const ast = parser.parse(TEX_SOURCE); | ||
|
||
const macroInfo: Record<string, string[]> = {}; | ||
const mathMacros: string[] = []; | ||
|
||
visit(ast, (node, info) => { | ||
if (!anyMacro(node)) { | ||
return; | ||
} | ||
// If we're here, we are a macro node. | ||
macroInfo[node.content] = macroInfo[node.content] || []; | ||
// `printRaw` will print `node` (and its content) without any formatting. | ||
macroInfo[node.content].push(printRaw(node)); | ||
|
||
// `info.context` contains information about where in the parse tree we currently are. | ||
if (info.context.inMathMode) { | ||
// Save just the macro "name". | ||
mathMacros.push(node.content); | ||
} | ||
}); | ||
|
||
// Prints | ||
// | ||
// ``` | ||
// Macro statistics: | ||
// | ||
// All macros: { | ||
// textbf: [ '\\textbf{an}', '\\textbf{amazing}' ], | ||
// LaTeX: [ '\\LaTeX' ], | ||
// textit: [ '\\textit{some}', '\\textit{\\textbf{amazing}}' ], | ||
// '^': [ '^{x}', '^{\\infty}', '^{n}' ], | ||
// sum: [ '\\sum' ], | ||
// _: [ '_{n=0}' ], | ||
// infty: [ '\\infty' ], | ||
// frac: [ '\\frac{x^{n}}{n!}' ] | ||
// } | ||
// Math mode macros: [ '^', 'sum', '_', '^', 'infty', 'frac', '^' ] | ||
// ``` | ||
console.log("Macro statistics:\n"); | ||
console.log("All macros:", macroInfo); | ||
console.log("Math mode macros:", mathMacros); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/** | ||
* This example shows how include your own macros for parsing. | ||
*/ | ||
import { unified } from "unified"; | ||
import { unifiedLatexFromString } from "@unified-latex/unified-latex-util-parse"; | ||
import { printRaw } from "@unified-latex/unified-latex-util-print-raw"; | ||
|
||
const TEX_SOURCE = String.raw` | ||
My \textbf{custom} \abc{macro}. | ||
`; | ||
|
||
// The default parser for `unified-latex` recognizes macros coming from several packages (those listed in `unified-latex-ctan/package`), | ||
// but you may want to add your own macros to the parsing pipeline. | ||
|
||
// Parser with defaults | ||
const processor1 = unified().use(unifiedLatexFromString); | ||
const ast1 = processor1.parse(TEX_SOURCE); | ||
// Prints `\textbf{custom} \abc`. Notice how the argument of `\xxx` is not included. | ||
console.log(printRaw(ast1.content[2]), printRaw(ast1.content[4])); | ||
|
||
/** | ||
* Adding a custom macro specification | ||
*/ | ||
|
||
// We can pass in custom macro (and environment) specifications to the parser. | ||
const processor2 = unified().use(unifiedLatexFromString, { | ||
// We define the macro `\abc` to take one mandatory argument. The `signature` is specified | ||
// using the syntax of the `xparse` package: https://ctan.org/pkg/xparse | ||
macros: { abc: { signature: "m" } }, | ||
}); | ||
const ast2 = processor2.parse(TEX_SOURCE); | ||
// Prints `\textbf{custom} \abc{macro}`. Notice how the argument of `\abc` is included. | ||
console.log(printRaw(ast2.content[2]), printRaw(ast2.content[4])); | ||
|
||
// Any specification you add take precedence over the built in ones. | ||
const processor3 = unified().use(unifiedLatexFromString, { | ||
macros: { textbf: { signature: "" }, abc: { signature: "m" } }, | ||
}); | ||
const ast3 = processor3.parse(TEX_SOURCE); | ||
// Prints `\textbf \abc{macro}`. | ||
// Notice how the argument of `\textbf` is not included. Te index of `\abc` also changed | ||
// because there are additional nodes (since `\textbf` didn't take its argument). | ||
console.log( | ||
printRaw(ast3.content[2]), | ||
printRaw(ast3.content[5]), | ||
printRaw(ast3.content[4]) | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
/** | ||
* This example shows how expand or replace macros. | ||
*/ | ||
import { unified, Plugin } from "unified"; | ||
import { unifiedLatexFromString } from "@unified-latex/unified-latex-util-parse"; | ||
import { printRaw } from "@unified-latex/unified-latex-util-print-raw"; | ||
import { replaceNode } from "@unified-latex/unified-latex-util-replace"; | ||
import { match } from "@unified-latex/unified-latex-util-match"; | ||
import { visit } from "@unified-latex/unified-latex-util-visit"; | ||
import { | ||
expandMacrosExcludingDefinitions, | ||
listNewcommands, | ||
} from "@unified-latex/unified-latex-util-macros"; | ||
import { attachMacroArgs } from "@unified-latex/unified-latex-util-arguments"; | ||
import * as Ast from "@unified-latex/unified-latex-types"; | ||
import { unifiedLatexStringCompiler } from "@unified-latex/unified-latex-util-to-string"; | ||
|
||
const TEX_SOURCE = String.raw` | ||
\newcommand{\abc}[1]{ABC} | ||
My \textbf{custom} \abc{macro}. | ||
`; | ||
|
||
/** | ||
* Replacing macros with `replaceNode` | ||
*/ | ||
|
||
// A common task is to replace a macro with some other content. One way to do this is with `replaceNode`. | ||
|
||
const processor1 = unified().use(unifiedLatexFromString, { | ||
// Parse `\abc` as taking a mandatory argument. | ||
macros: { abc: { signature: "m" } }, | ||
}); | ||
const ast1 = processor1.parse(TEX_SOURCE); | ||
// Prints: `\newcommand{\abc}[1]{ABC} My \textbf{custom} \abc{macro}.` | ||
console.log(printRaw(ast1)); | ||
|
||
replaceNode(ast1, (node) => { | ||
if (match.macro(node, "newcommand")) { | ||
// Remove any `\newcommand` macros from the tree. | ||
return null; | ||
} | ||
if (match.macro(node, "abc")) { | ||
// Replace `\abc` with `ABC`. | ||
return { type: "string", content: "ABC" }; | ||
} | ||
}); | ||
// Prints: ` My \textbf{custom} ABC.` | ||
console.log(printRaw(ast1)); | ||
|
||
/** | ||
* Replacing macros only in math mode | ||
*/ | ||
|
||
// Using the `info` object, you can get extra information about context before replacing a node. | ||
const ast2 = processor1.parse(String.raw`\abc{fun} $x=\abc{times}$`); | ||
replaceNode(ast2, (node, info) => { | ||
if (info.context.inMathMode && match.macro(node, "abc")) { | ||
// Replace `\abc` with `ABC` only in math mode. | ||
return { type: "string", content: "ABC" }; | ||
} | ||
}); | ||
// Prints: `\abc{fun} $x=ABC$` | ||
console.log(printRaw(ast2)); | ||
|
||
/** | ||
* Replacing during `visit` | ||
*/ | ||
|
||
// `replaceNode` is really just a wrapper around `visit`. You can use `visit` directly to replace nodes. | ||
const ast3 = processor1.parse(TEX_SOURCE); | ||
visit(ast3, (node, info) => { | ||
if (match.macro(node, "newcommand")) { | ||
// Replace `\newcommand` with the empty string. | ||
// `replaceNode` actually _removes_ nodes from the tree, which we could do too, | ||
// but it would involve quite a bit more work. | ||
|
||
// We are directly manipulating a node and changing its type, | ||
// TypeScript doesn't like this, so we have to do some casting. | ||
node = node as unknown as Ast.String; | ||
node.type = "string"; | ||
node.content = ""; | ||
} | ||
if (match.macro(node, "abc")) { | ||
// Replace `\abc` with `ABC`. | ||
|
||
// We are directly manipulating a node and changing its type, | ||
// TypeScript doesn't like this, so we have to do some casting. | ||
node = node as unknown as Ast.String; | ||
node.type = "string"; | ||
node.content = "ABC"; | ||
} | ||
}); | ||
// Prints: ` My \textbf{custom} ABC.` | ||
console.log(printRaw(ast3)); | ||
|
||
/** | ||
* Expanding `\newcommand` directly | ||
*/ | ||
|
||
// We can expand macros defined via `\newcommand`, `\NewDocumentCommand`, etc. by creating a plugin. | ||
|
||
/** | ||
* Plugin that expands the specified macros by name. These macros must be defined in the document via | ||
* `\newcommand...` or equivalent. | ||
*/ | ||
export const expandDocumentMacrosPlugin: Plugin<void[], Ast.Root, Ast.Root> = | ||
function () { | ||
return (tree) => { | ||
const newcommands = listNewcommands(tree); | ||
|
||
const macroInfo = Object.fromEntries( | ||
newcommands.map((m) => [m.name, { signature: m.signature }]) | ||
); | ||
// We need to attach the arguments to each macro before we process it! | ||
attachMacroArgs(tree, macroInfo); | ||
// We want to expand all macros, except ones mentioned in actual `\newcommand` commands. | ||
expandMacrosExcludingDefinitions(tree, newcommands); | ||
|
||
// Finally, let's remove the `\newcommand`s from the tree. | ||
// Our document could have used `\newcommand` or `\NewDocumentCommand`, etc. We will remove | ||
// all of these. | ||
const newcommandsUsed = Object.fromEntries( | ||
newcommands.map((x) => [x.definition.content, true]) | ||
); | ||
replaceNode(tree, (node) => { | ||
if (match.anyMacro(node) && newcommandsUsed[node.content]) { | ||
return null; | ||
} | ||
}); | ||
}; | ||
}; | ||
|
||
const processor4 = unified() | ||
.use(unifiedLatexFromString) | ||
.use(expandDocumentMacrosPlugin) | ||
.use(unifiedLatexStringCompiler, { pretty: true }); | ||
const processed4 = processor4.processSync(TEX_SOURCE); | ||
// Prints: ` My \textbf{custom} ABC.` | ||
console.log(String(processed4)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/** | ||
* This example shows how ignore all default parsing and use exclusively custom macros. | ||
*/ | ||
import { unified } from "unified"; | ||
import { | ||
unifiedLatexAstComplier, | ||
unifiedLatexFromStringMinimal, | ||
unifiedLatexProcessMacrosAndEnvironmentsWithMathReparse, | ||
} from "@unified-latex/unified-latex-util-parse"; | ||
import { printRaw } from "@unified-latex/unified-latex-util-print-raw"; | ||
import { macros as xcolorMacros } from "@unified-latex/unified-latex-ctan/package/xcolor"; | ||
import { Root } from "@unified-latex/unified-latex-types"; | ||
|
||
const TEX_SOURCE = String.raw` | ||
My \textbf{custom} \abc{macro}. | ||
`; | ||
|
||
// The default parser for `unified-latex` recognizes macros coming from several packages (those listed in `unified-latex-ctan/package`), | ||
// but your use case may involve only custom macros (or you may want the speed boost of not processing many macros). | ||
// Parsing with `unifiedLatexFromStringMinimal` parses a string into its "most abstract" form, where no macro arguments are attached. | ||
// This means that a string like `\textbf{foo}` will be parsed as the macro `\textbf` followed by the group containing `foo`. | ||
|
||
// Parser with defaults | ||
const processor1 = unified().use(unifiedLatexFromStringMinimal); | ||
const ast1 = processor1.parse(TEX_SOURCE); | ||
// Prints `\textbf \abc`. Notice how `\xxx` is at position 3 (instead of 2 like in `custom-macros.ts`). | ||
// This is because `unifiedLatexFromStringMinimal` doesn't trim any leading or trailing whitespace. | ||
console.log(printRaw(ast1.content[3]), printRaw(ast1.content[6])); | ||
|
||
// You may want to process a string as if it were in math mode. This can be done by setting `mode: "math"` in the parser options. | ||
const processor2 = unified().use(unifiedLatexFromStringMinimal, { | ||
mode: "math", | ||
}); | ||
const ast2 = processor2.parse(`x^2`); | ||
// Prints `^`. | ||
console.log(printRaw(ast2.content[1])); | ||
|
||
/** | ||
* Using specific packages | ||
*/ | ||
|
||
// We can build a parsing pipeline that only recognizes macros from specific packages. | ||
const processor3 = unified() | ||
.use(unifiedLatexFromStringMinimal) | ||
// We could manually use `attachMacroArgs` and write a custom plugin, | ||
// but the `unifiedLatexProcessMacrosAndEnvironmentsWithMathReparse` is already here for us. | ||
// It will also reparse the content of custom "math" environments so their content is in math mode. | ||
// (Ths is how `\begin{equation}...\end{equation}` end up with their contents parsed in math mode.) | ||
.use(unifiedLatexProcessMacrosAndEnvironmentsWithMathReparse, { | ||
// Only process macros from the `xcolor` package. | ||
macros: xcolorMacros, | ||
environments: {}, | ||
}) | ||
.use(unifiedLatexAstComplier); | ||
const processed3 = processor3.processSync(String.raw`\color{blue}\textbf{foo}`) | ||
.result as Root; | ||
// Print the parsed AST with a space between each node. | ||
// Prints `\color{blue} \textbf {foo}`. | ||
console.log(processed3.content.map((c) => printRaw(c)).join(" ")); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"compilerOptions": { "rootDir": "./" }, | ||
"include": ["./**/*.ts"], | ||
"extends": "../tsconfig.build.json", | ||
} |
Oops, something went wrong.