| // Parser MathBlock | |
| var latexMathParser = (function() { | |
| function commandToBlock(cmd) { // can also take in a Fragment | |
| var block = MathBlock(); | |
| cmd.adopt(block, 0, 0); | |
| return block; | |
| } | |
| function joinBlocks(blocks) { | |
| var firstBlock = blocks[0] || MathBlock(); | |
| for (var i = 1; i < blocks.length; i += 1) { | |
| blocks[i].children().adopt(firstBlock, firstBlock.ends[R], 0); | |
| } | |
| return firstBlock; | |
| } | |
| var string = Parser.string; | |
| var regex = Parser.regex; | |
| var letter = Parser.letter; | |
| var any = Parser.any; | |
| var optWhitespace = Parser.optWhitespace; | |
| var succeed = Parser.succeed; | |
| var fail = Parser.fail; | |
| // Parsers yielding either MathCommands, or Fragments of MathCommands | |
| // (either way, something that can be adopted by a MathBlock) | |
| var variable = letter.map(function(c) { return Letter(c); }); | |
| var symbol = regex(/^[^${}\\_^]/).map(function(c) { return VanillaSymbol(c); }); | |
| var controlSequence = | |
| regex(/^[^\\a-eg-zA-Z]/) // hotfix #164; match MathBlock::write | |
| .or(string('\\').then( | |
| regex(/^[a-z]+/i) | |
| .or(regex(/^\s+/).result(' ')) | |
| .or(any) | |
| )).then(function(ctrlSeq) { | |
| var cmdKlass = LatexCmds[ctrlSeq]; | |
| if (cmdKlass) { | |
| return cmdKlass(ctrlSeq).parser(); | |
| } | |
| else { | |
| return fail('unknown command: \\'+ctrlSeq); | |
| } | |
| }) | |
| ; | |
| var command = | |
| controlSequence | |
| .or(variable) | |
| .or(symbol) | |
| ; | |
| // Parsers yielding MathBlocks | |
| var mathGroup = string('{').then(function() { return mathSequence; }).skip(string('}')); | |
| var mathBlock = optWhitespace.then(mathGroup.or(command.map(commandToBlock))); | |
| var mathSequence = mathBlock.many().map(joinBlocks).skip(optWhitespace); | |
| var optMathBlock = | |
| string('[').then( | |
| mathBlock.then(function(block) { | |
| return block.join('latex') !== ']' ? succeed(block) : fail(); | |
| }) | |
| .many().map(joinBlocks).skip(optWhitespace) | |
| ).skip(string(']')) | |
| ; | |
| var latexMath = mathSequence; | |
| latexMath.block = mathBlock; | |
| latexMath.optBlock = optMathBlock; | |
| return latexMath; | |
| })(); | |
| Controller.open(function(_, super_) { | |
| _.exportLatex = function() { | |
| return this.root.latex().replace(/(\\[a-z]+) (?![a-z])/ig,'$1'); | |
| }; | |
| _.writeLatex = function(latex) { | |
| var cursor = this.notify('edit').cursor; | |
| var all = Parser.all; | |
| var eof = Parser.eof; | |
| var block = latexMathParser.skip(eof).or(all.result(false)).parse(latex); | |
| if (block && !block.isEmpty()) { | |
| block.children().adopt(cursor.parent, cursor[L], cursor[R]); | |
| var jQ = block.jQize(); | |
| jQ.insertBefore(cursor.jQ); | |
| cursor[L] = block.ends[R]; | |
| block.finalizeInsert(cursor.options, cursor); | |
| if (block.ends[R][R].siblingCreated) block.ends[R][R].siblingCreated(cursor.options, L); | |
| if (block.ends[L][L].siblingCreated) block.ends[L][L].siblingCreated(cursor.options, R); | |
| cursor.parent.bubble('reflow'); | |
| } | |
| return this; | |
| }; | |
| _.renderLatexMath = function(latex) { | |
| var root = this.root, cursor = this.cursor; | |
| var all = Parser.all; | |
| var eof = Parser.eof; | |
| var block = latexMathParser.skip(eof).or(all.result(false)).parse(latex); | |
| root.eachChild('postOrder', 'dispose'); | |
| root.ends[L] = root.ends[R] = 0; | |
| if (block) { | |
| block.children().adopt(root, 0, 0); | |
| } | |
| var jQ = root.jQ; | |
| if (block) { | |
| var html = block.join('html'); | |
| jQ.html(html); | |
| root.jQize(jQ.children()); | |
| root.finalizeInsert(cursor.options); | |
| } | |
| else { | |
| jQ.empty(); | |
| } | |
| delete cursor.selection; | |
| cursor.insAtRightEnd(root); | |
| }; | |
| _.renderLatexText = function(latex) { | |
| var root = this.root, cursor = this.cursor; | |
| root.jQ.children().slice(1).remove(); | |
| root.eachChild('postOrder', 'dispose'); | |
| root.ends[L] = root.ends[R] = 0; | |
| delete cursor.selection; | |
| cursor.show().insAtRightEnd(root); | |
| var regex = Parser.regex; | |
| var string = Parser.string; | |
| var eof = Parser.eof; | |
| var all = Parser.all; | |
| // Parser RootMathCommand | |
| var mathMode = string('$').then(latexMathParser) | |
| // because TeX is insane, math mode doesn't necessarily | |
| // have to end. So we allow for the case that math mode | |
| // continues to the end of the stream. | |
| .skip(string('$').or(eof)) | |
| .map(function(block) { | |
| // HACK FIXME: this shouldn't have to have access to cursor | |
| var rootMathCommand = RootMathCommand(cursor); | |
| rootMathCommand.createBlocks(); | |
| var rootMathBlock = rootMathCommand.ends[L]; | |
| block.children().adopt(rootMathBlock, 0, 0); | |
| return rootMathCommand; | |
| }) | |
| ; | |
| var escapedDollar = string('\\$').result('$'); | |
| var textChar = escapedDollar.or(regex(/^[^$]/)).map(VanillaSymbol); | |
| var latexText = mathMode.or(textChar).many(); | |
| var commands = latexText.skip(eof).or(all.result(false)).parse(latex); | |
| if (commands) { | |
| for (var i = 0; i < commands.length; i += 1) { | |
| commands[i].adopt(root, root.ends[R], 0); | |
| } | |
| root.jQize().appendTo(root.jQ); | |
| root.finalizeInsert(cursor.options); | |
| } | |
| }; | |
| }); |