Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"presets": ["es2015", "stage-2"],
"plugins": []
}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# dependencies
/node_modules

## emacs
*~

Expand All @@ -13,5 +16,6 @@ rails/out

# generated files #
###################
/latex
rails-html
sicp.zip
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,17 @@ Static site generation is handled by `rails/Rakefile`. It outputs the html pages
## Bugs
* Google Custom Search doesn't seem to be working.
* The snippets containing '\n' would lead to newline rather than showing charater '\n' in the `Source Academy` platform. This problem is difficult to eliminate because the code encoder cannot differentiate them when reading content.

## XML2Latex

## Requirements
For development & deployment:
* node.js

## Set up
Run `npm install` to install dependencies.

## Generating Latex Files
Run `npm start`.
Latex files will be in the latex folder.
Compile main.tex with XeLaTex+MakeIndex+BibTex for the pdf version.
153 changes: 153 additions & 0 deletions nodejs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
const fs = require('fs');
const path = require('path');

const xpath = require('xpath');
const dom = require('xmldom').DOMParser;

import parseXML from './parseXML.js';

const inputDir = path.join(__dirname, '../xml');
const outputDir = path.join(__dirname, '../latex');

const preamble = `\\documentclass{report}

\\usepackage{amsmath}
\\usepackage{amssymb}
\\usepackage{cprotect}
\\usepackage{csquotes}
\\usepackage{epigraph}
\\usepackage{etoolbox}
\\usepackage{graphicx}
\\usepackage{listings}
\\usepackage{makeidx}
\\usepackage{subcaption}
\\usepackage{underscore}

\\graphicspath{ {../rails/public/chapters/} }

\\expandafter\\patchcmd\\csname \\string\\lstinline\\endcsname{%
\\leavevmode
\\bgroup
}{%
\\leavevmode
\\ifmmode\\hbox\\fi
\\bgroup
}{}{%
\\typeout{Patching of \\string\\lstinline\\space failed!}%
}

\\lstdefinelanguage{JavaScript}{
keywords={const, break, case, catch, continue, debugger, default, delete, do, else, finally, for, function, if, in, instanceof, new, return, switch, this, throw, try, typeof, var, void, while, with},
morecomment=[l]{//},
morecomment=[s]{/*}{*/},
morestring=[b]',
morestring=[b]",
sensitive=true
}

\\lstset{
language=JavaScript,
basicstyle=\\ttfamily,
showstringspaces=false,
showspaces=false,
escapechar={^}
}

\\newcommand{\\lt}{\\symbol{"3C}}% Less than
\\newcommand{\\gt}{\\symbol{"3E}}% Greater than

\\setlength\\epigraphwidth{11cm}
\\setlength\\epigraphrule{0pt}

\\makeindex

\\begin{document}\n`;

const ending = `\n\\printindex\n
\\end{document}`;

const ensureDirectoryExists = (path, cb) => {
fs.mkdir(path, (err) => {
if (err) {
if (err.code == 'EEXIST') cb(null); // ignore the error if the folder already exists
else cb(err); // something else went wrong
} else cb(null); // successfully created folder
});
}

const xmlToLatex = (filepath, filename) => {
const fullFilepath = path.join(inputDir, filepath, filename);
fs.open(fullFilepath, 'r', (err, fileToRead) => {
if (err) {
console.log(err);
return;
}
fs.readFile(fileToRead, {encoding: 'utf-8'}, (err,data) => {
if (err) {
console.log(err);
return;
}
const doc = new dom().parseFromString(data);
const writeTo = [];

parseXML(doc.documentElement, writeTo);
ensureDirectoryExists(path.join(outputDir, filepath), (err) => {
if (err) {
console.log(err);
return;
}
const outputFile = path.join(outputDir, filepath, filename.replace(/\.xml$/, '') + '.tex');
const stream = fs.createWriteStream(outputFile);
stream.once('open', (fd) => {
stream.write(writeTo.join(""));
stream.end();
});
});
});
});
}

const recursiveXmlToLatex = (filepath) => {
const fullPath = path.join(inputDir, filepath);
fs.readdir(fullPath, (err, files) => {
files.forEach(file => {
if (file.match(/\.xml$/)) {
// console.log(file + " being processed");
xmlToLatex(filepath, file);
}
else if (fs.lstatSync(path.join(fullPath, file)).isDirectory()){
recursiveXmlToLatex(path.join(filepath, file));
}
});
});
}

const createMainLatex = () => {
const chaptersFound = [];
const files = fs.readdirSync(inputDir);
files.forEach(file => {
if (file.match(/chapter/)) {
chaptersFound.push(file);
}
});
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
}
const stream = fs.createWriteStream(path.join(outputDir, "main.tex"));
stream.once('open', (fd) => {
stream.write(preamble);
chaptersFound.forEach(chapter => {
const pathStr = chapter + "/" + chapter + ".tex";
stream.write("\\input{" + pathStr + "}\n");
});
stream.write(ending);
stream.end();
});
}

const main = () => {
createMainLatex();
recursiveXmlToLatex('');
}

main();
190 changes: 190 additions & 0 deletions nodejs/parseText.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import replaceTagWithSymbol from './replaceTagWithSymbol';
import processFigure from './processFigure';

const tagsToRemove = new Set(["#comment", "COMMENT", "CHANGE", "EXCLUDE", "HISTORY", "SCHEME", "SCHEMEINLINE", "EXERCISE", "SOLUTION"]);
const ignoreTags = new Set(["JAVASCRIPT", "SPLIT", "SPLITINLINE", "NOBR"]);

export const processTextFunctions = {
"#text": ((node, writeTo) => {
const trimedValue = node.nodeValue.replace(/[\r\n]+/, " ").replace(/\s+/g, " ");
if (!trimedValue.match(/^\s*$/)) {
writeTo.push(trimedValue.replace(/%/g, "\\%"));
}
}),

"B": ((node, writeTo) => {
writeTo.push("\\textbf{");
recursiveProcessText(node.firstChild, writeTo);
writeTo.push("}");
}),

"BLOCKQUOTE": ((node, writeTo) => {
writeTo.push("\n\\begin{quote}");
recursiveProcessText(node.firstChild, writeTo);
writeTo.push("\\end{quote}\n");
}),

"EM": ((node, writeTo) => processTextFunctions["em"](node, writeTo)),
"em": ((node, writeTo) => {
writeTo.push("{\\em ");
recursiveProcessText(node.firstChild, writeTo);
writeTo.push("}");
}),

"FIGURE": ((node, writeTo) => {
processFigure(node, writeTo);
}),

"IMAGE": ((node, writeTo) => {
writeTo.push("\n\\includegraphics{"
+ node.getAttribute("src").replace(/\.gif$/, ".png").replace(/_/g, "\\string_")
+ "}\n");
}),

"FOOTNOTE": ((node, writeTo) => {
writeTo.push("\n\\cprotect\\footnote{");
recursiveProcessText(node.firstChild, writeTo);
writeTo.push("}\n");
}),

"INDEX": ((node, writeTo) => {
processIndex(node, writeTo);
}),

"LABEL": ((node, writeTo) => {
writeTo.push("\\label{"
+ node.getAttribute("NAME")
+ "}\n");
}),

"LATEX": ((node, writeTo) => processTextFunctions["LATEXINLINE"](node, writeTo)),
"LATEXINLINE": ((node, writeTo) => {
recursiveProcessPureText(node.firstChild, writeTo);
}),

"NAME": ((node, writeTo) => {
recursiveProcessText(node.firstChild, writeTo);
writeTo.push("}\n");
}),

"OL": ((node, writeTo) => {
writeTo.push("\n\\begin{enumerate}\n");
processList(node.firstChild, writeTo);
writeTo.push("\\end{enumerate}\n");
}),

"P": ((node, writeTo) => processTextFunctions["TEXT"](node, writeTo)),
"TEXT": ((node, writeTo) => {
writeTo.push("\n\n");
recursiveProcessText(node.firstChild, writeTo);
writeTo.push("\n");
}),

"QUOTE": ((node, writeTo) => {
writeTo.push("\\enquote{");
recursiveProcessText(node.firstChild, writeTo);
writeTo.push("}");
}),

"REF": ((node, writeTo) => {
writeTo.push("~\\ref{"
+ node.getAttribute("NAME")
+ "}");
}),

"SCHEMEINLINE": ((node, writeTo) => processTextFunctions["JAVASCRIPTINLINE"](node, writeTo)),
"JAVASCRIPTINLINE": ((node, writeTo) => {
writeTo.push("\\lstinline|");
recursiveProcessPureText(node.firstChild, writeTo, true);
writeTo.push("|");
}),

"SNIPPET": ((node, writeTo) => {
processSnippet(node, writeTo);
}),

"SUBHEADING": ((node, writeTo) => {
writeTo.push("\\subsubsection{");
recursiveProcessText(node.firstChild, writeTo);
}),

"UL": ((node, writeTo) => {
writeTo.push("\n\\begin{itemize}\n");
processList(node.firstChild, writeTo);
writeTo.push("\\end{itemize}\n");
})
}

export const processList = (node, writeTo) => {
if (!node) return;
if (node.nodeName == "LI"){
writeTo.push("\\item{");
recursiveProcessText(node.firstChild, writeTo)
writeTo.push("}\n");
}
return processList(node.nextSibling, writeTo);
}

export const processSnippet = (node, writeTo) => {
const jsSnippet = node.getElementsByTagName("JAVASCRIPT")[0];
if (jsSnippet) {
writeTo.push("\n\\begin{lstlisting}");
recursiveProcessPureText(jsSnippet.firstChild, writeTo);
writeTo.push("\\end{lstlisting}\n");
}
}

const recursiveProcessPureText = (node, writeTo, removeNewline = false) => {
if (!node) return;
if (!replaceTagWithSymbol(node, writeTo)) {
if (removeNewline) {
writeTo.push(node.nodeValue.replace(/[\r\n]+/g, " "));
} else {
writeTo.push(node.nodeValue);
}
}
return recursiveProcessPureText(node.nextSibling, writeTo)
}

export const recursiveProcessText = (node, writeTo) => {
if (!node) return;
if (!processText(node, writeTo)){
console.log("recusive process:\n" + node.toString());
}
return recursiveProcessText(node.nextSibling, writeTo)
}

export const processText = (node, writeTo) => {
const name = node.nodeName;
if (processTextFunctions[name]) {
processTextFunctions[name](node, writeTo);
return true;
} else {
if (replaceTagWithSymbol(node, writeTo) || tagsToRemove.has(name)) {
return true;
} else if (ignoreTags.has(name)) {
recursiveProcessText(node.firstChild, writeTo);
return true;
} else {
return false;
}
}
}

export const processIndex = (index, writeTo) => {
writeTo.push("\\index{");
for (let child = index.firstChild; child; child = child.nextSibling) {
const name = child.nodeName;
switch (name) {
case "SUBINDEX":
writeTo.push("!");
recursiveProcessText(child.firstChild, writeTo);
break;

default:
processText(child, writeTo);
}
}
writeTo.push("}");
}

Loading