diff --git a/docs/expressions/customization.md b/docs/expressions/customization.md index 245ba7df7..543fb4944 100644 --- a/docs/expressions/customization.md +++ b/docs/expressions/customization.md @@ -139,3 +139,95 @@ math.import({ math.eval('myFunction(2 + 3, sqrt(4))'); // returns 'arguments: 2 + 3, sqrt(4), evaluated: 5, 2' ``` + +## Custom LaTeX conversion + +You can provide the `toTex` function of an expression tree with your own LaTeX converters. +This can be used to override the builtin LaTeX conversion or provide LaTeX output for your own custom functions. + +You can pass your own callback(s) to `toTex`. If it returns nothing, the standard LaTeX conversion will be use. +If your callback returns a string, this string will be used. + +There's two ways of passing callbacks: +1. Pass an object that maps function names to callbacks. Those callbacks will be used for FunctionNodes with +functions of that name. +2. Pass a function to `toTex`. This function will then be used for every node. + + +**Examples for option 2** +```js +var customFunctions = { + binomial: function (n, k) { + //calculate n choose k + // (do some stuff) + return result; + } +}; + +var customLaTeX = { + 'binomial': function (node, callbacks) { //provide toTex for your own custom function + return '\\binom{' + node.args[0].toTex(callbacks) + '}{' + node.args[1].toTex(callbacks) + '}'; + }, + 'factorial': function (node, callbacks) { //override toTex for builtin functions + return 'factorial\\left(' + node.args[0] + '\\right)'; + } +}; +``` + +You can simply use your custom toTex functions by passing them to `toTex`: +```js +math.import(customFunctions); +var expression = math.parse('binomial(factorial(2),1)'); +var latex = expression.toTex(customLaTeX); +//latex now contains "\binom{factorial\\left(2\\right)}{1}" +``` + +**Examples for option 2:** + +```js +var customLaTeX = function (node, callback) { + if ((node.type === 'OperatorNode') && (node.fn === 'add')) { + //don't forget to pass the callback to the toTex functions + return node.args[0].toTex(callback) + ' plus ' + node.args[1].toTex(callback); + } + else if (node.type === 'ConstantNode') { + if (node.value == 0) { + return '\\mbox{zero}'; + } + else if (node.value == 1) { + return '\\mbox{one}'; + } + else if (node.value == 2) { + return '\\mbox{two}'; + } + else { + return node.value; + } + } +}; + +var expression = math.parse('1+2'); +var latex = expression.toTex(customLaTeX); +//latex now contains '\mbox{one} plus \mbox{two}' +``` +Another example in conjunction with custom functions: +```js +var customFunctions = { + binomial: function (n, k) { + //calculate n choose k + // (do some stuff) + return result; + } +}; + +var customLaTeX = function (node, callback) { + if ((node.type === 'FunctionNode') && (node.name === 'binomial')) { + return '\\binom{' + node.args[0].toTex(callback) + '}{' + node.args[1].toTex(callback) + '}'; + } +}; + +math.import(customFunctions); +var expression = math.parse('binomial(2,1)'); +var latex = expression.toTex(customLaTeX); +//latex now contains "\binom{2}{1}" +``` diff --git a/lib/expression/node/ArrayNode.js b/lib/expression/node/ArrayNode.js index fbbb67bf7..c4636c06c 100644 --- a/lib/expression/node/ArrayNode.js +++ b/lib/expression/node/ArrayNode.js @@ -95,26 +95,28 @@ ArrayNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) + * @param {String} type * @return {String} str */ -ArrayNode.prototype.toTex = function(type) { - type = type || 'bmatrix'; - var s = '\\begin{' + type + '}'; +ArrayNode.prototype._toTex = function(callbacks) { + this.latexType = this.latexType || 'bmatrix'; + var s = '\\begin{' + this.latexType + '}'; this.nodes.forEach(function(node) { if (node.nodes) { s += node.nodes.map(function(childNode) { - return childNode.toTex(); + return childNode.toTex(callbacks); }).join('&'); } else { - s += node.toTex(); + s += node.toTex(callbacks); } // new line s += '\\\\'; }); - s += '\\end{' + type + '}'; + s += '\\end{' + this.latexType + '}'; return s; }; diff --git a/lib/expression/node/AssignmentNode.js b/lib/expression/node/AssignmentNode.js index 90fb93373..e47e22349 100644 --- a/lib/expression/node/AssignmentNode.js +++ b/lib/expression/node/AssignmentNode.js @@ -89,13 +89,14 @@ AssignmentNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} */ -AssignmentNode.prototype.toTex = function() { +AssignmentNode.prototype._toTex = function(callbacks) { var precedence = operators.getPrecedence(this); var exprPrecedence = operators.getPrecedence(this.expr); - var expr = this.expr.toTex(); + var expr = this.expr.toTex(callbacks); if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) { //adds visible round brackets expr = latex.addBraces(expr, true); diff --git a/lib/expression/node/BlockNode.js b/lib/expression/node/BlockNode.js index 57c2e4e03..3bccddbd1 100644 --- a/lib/expression/node/BlockNode.js +++ b/lib/expression/node/BlockNode.js @@ -122,11 +122,12 @@ BlockNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str */ -BlockNode.prototype.toTex = function() { +BlockNode.prototype._toTex = function(callbacks) { return this.blocks.map(function (param) { - return param.node.toTex() + (param.visible ? '' : ';'); + return param.node.toTex(callbacks) + (param.visible ? '' : ';'); }).join('\n'); }; diff --git a/lib/expression/node/ConditionalNode.js b/lib/expression/node/ConditionalNode.js index 54ea5c02f..e5acb04a5 100644 --- a/lib/expression/node/ConditionalNode.js +++ b/lib/expression/node/ConditionalNode.js @@ -151,15 +151,16 @@ ConditionalNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str */ -ConditionalNode.prototype.toTex = function() { +ConditionalNode.prototype._toTex = function(callbacks) { var s = ( - latex.addBraces(this.trueExpr.toTex()) + + latex.addBraces(this.trueExpr.toTex(callbacks)) + ', &\\quad' + - latex.addBraces('\\text{if}\\;' + this.condition.toTex()) + latex.addBraces('\\text{if}\\;' + this.condition.toTex(callbacks)) ) + '\\\\' + ( - latex.addBraces(this.falseExpr.toTex()) + + latex.addBraces(this.falseExpr.toTex(callbacks)) + ', &\\quad' + latex.addBraces('\\text{otherwise}') ); diff --git a/lib/expression/node/ConstantNode.js b/lib/expression/node/ConstantNode.js index 5b47d40e8..10e8448cf 100644 --- a/lib/expression/node/ConstantNode.js +++ b/lib/expression/node/ConstantNode.js @@ -156,9 +156,10 @@ ConstantNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str */ -ConstantNode.prototype.toTex = function() { +ConstantNode.prototype._toTex = function(callbacks) { var value = this.value, index; switch (this.valueType) { diff --git a/lib/expression/node/FunctionAssignmentNode.js b/lib/expression/node/FunctionAssignmentNode.js index 629360ca1..03fe8f43d 100644 --- a/lib/expression/node/FunctionAssignmentNode.js +++ b/lib/expression/node/FunctionAssignmentNode.js @@ -110,13 +110,14 @@ FunctionAssignmentNode.prototype.toString = function() { /** * get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str */ -FunctionAssignmentNode.prototype.toTex = function() { +FunctionAssignmentNode.prototype._toTex = function(callbacks) { var precedence = operators.getPrecedence(this); var exprPrecedence = operators.getPrecedence(this.expr); - var expr = this.expr.toTex(); + var expr = this.expr.toTex(callbacks); if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) { //adds visible round brackets expr = latex.addBraces(expr, true); @@ -127,7 +128,7 @@ FunctionAssignmentNode.prototype.toTex = function() { } return latex.toFunction(this.name) - + latex.addBraces(this.params.map(latex.toSymbol).join(', '), true) + '=' + + latex.addBraces(this.params.map(latex.toSymbol).join(', '), true) + '=' //FIXME, this doesn't call toTex on the parameters AFAIK (toSymbol) + expr; }; diff --git a/lib/expression/node/FunctionNode.js b/lib/expression/node/FunctionNode.js index b36255502..fcce31025 100644 --- a/lib/expression/node/FunctionNode.js +++ b/lib/expression/node/FunctionNode.js @@ -113,10 +113,11 @@ FunctionNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str */ -FunctionNode.prototype.toTex = function() { - return latex.toArgs(this); +FunctionNode.prototype._toTex = function(callbacks) { + return latex.toArgs(this, callbacks); }; /** diff --git a/lib/expression/node/IndexNode.js b/lib/expression/node/IndexNode.js index cb8519a22..ed12171f7 100644 --- a/lib/expression/node/IndexNode.js +++ b/lib/expression/node/IndexNode.js @@ -209,10 +209,11 @@ IndexNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str */ -IndexNode.prototype.toTex = function() { - return this.object.toTex() + '[' + this.ranges.join(', ') + ']'; +IndexNode.prototype._toTex = function(callbacks) { + return this.object.toTex(callbacks) + '[' + this.ranges.join(', ') + ']'; }; -module.exports = IndexNode; \ No newline at end of file +module.exports = IndexNode; diff --git a/lib/expression/node/Node.js b/lib/expression/node/Node.js index 5b97d5693..7d4813dd0 100644 --- a/lib/expression/node/Node.js +++ b/lib/expression/node/Node.js @@ -221,11 +221,62 @@ Node.prototype.toString = function() { }; /** - * Get LaTeX representation + * Get LaTeX representation. (wrapper function) + * This functions get's either an object containing callbacks or + * a single callback. It decides whether to call the callback and if + * not or if the callback returns nothing, it calls the default + * LaTeX implementation of the node (_toTex). + * + * @param {Object|function} callback(s) * @return {String} */ -Node.prototype.toTex = function() { - return ''; +Node.prototype.toTex = function(callback) { + var customTex; + if (this.type === 'ArrayNode') { + //FIXME this is only a workaround for a breaking change, + //remove this in version2 + delete this.latexType; + } + if (typeof callback === 'object') { + if ((this.type === 'FunctionNode') && callback.hasOwnProperty(this.name)) { + //if callback is a map of callback functions and this is a FunctionNode + customTex = callback[this.name](this, callback); + } + } + else if (typeof callback === 'function') { + //if callback is a function + customTex = callback(this, callback); + } + else if ((typeof callback === 'string') && (this.type === 'ArrayNode')) { + //FIXME this is only a workaround for a breaking change, + //remove this in version2 + this.latexType = callback; + } + else if (typeof callback !== 'undefined') { + throw new TypeError('Object or function expected as callback'); + } + + if (typeof customTex !== 'undefined') { + return customTex; + } + + return this._toTex(callback); +}; + +/** + * Internal function to generate the LaTeX output. + * This has to be implemented by every Node + * + * @param {Object}|function} + * @throws {Error} + */ +Node.prototype._toTex = function () { + if (this.type === 'Node') { + //FIXME remove this in v2 + return ''; + } + //must be implemented by each of the Node implementations + throw new Error('_toTex not implemented for this Node'); }; /** diff --git a/lib/expression/node/OperatorNode.js b/lib/expression/node/OperatorNode.js index 5c97904e4..ee8db2afa 100644 --- a/lib/expression/node/OperatorNode.js +++ b/lib/expression/node/OperatorNode.js @@ -236,9 +236,10 @@ OperatorNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str */ -OperatorNode.prototype.toTex = function() { +OperatorNode.prototype._toTex = function(callbacks) { var args = this.args; var parens = calculateNecessaryParentheses(this, args); var op = latex.toOperator(this.op); //operator @@ -247,7 +248,7 @@ OperatorNode.prototype.toTex = function() { case 1: //unary operators var assoc = operators.getAssociativity(this); - var operand = args[0].toTex(); + var operand = args[0].toTex(callbacks); if (parens[0]) { operand = latex.addBraces(operand, true); } @@ -265,9 +266,9 @@ OperatorNode.prototype.toTex = function() { case 2: //binary operators var lhs = args[0]; //left hand side //reminder: if parens[0] is false, this puts it in curly braces - var lhsTex = latex.addBraces(lhs.toTex(), parens[0]); + var lhsTex = latex.addBraces(lhs.toTex(callbacks), parens[0]); var rhs = args[1]; //right hand side - var rhsTex = latex.addBraces(rhs.toTex(), parens[1]); + var rhsTex = latex.addBraces(rhs.toTex(callbacks), parens[1]); switch (this.getIdentifier()) { case 'OperatorNode:divide': @@ -275,7 +276,7 @@ OperatorNode.prototype.toTex = function() { return op + lhsTex + rhsTex; case 'OperatorNode:to': - rhsTex = latex.toUnit(rhs.toTex()); + rhsTex = latex.toUnit(rhs.toTex(callbacks)); rhsTex = latex.addBraces(rhsTex, parens[1]); break; } diff --git a/lib/expression/node/RangeNode.js b/lib/expression/node/RangeNode.js index f1f08b2bb..d3af794ee 100644 --- a/lib/expression/node/RangeNode.js +++ b/lib/expression/node/RangeNode.js @@ -121,14 +121,15 @@ RangeNode.prototype.toString = function() { /** * Get LaTeX representation + * @params {Object|function} callback(s) * @return {String} str */ -RangeNode.prototype.toTex = function() { - var str = this.start.toTex(); +RangeNode.prototype._toTex = function(callbacks) { + var str = this.start.toTex(callbacks); if (this.step) { - str += ':' + this.step.toTex(); + str += ':' + this.step.toTex(callbacks); } - str += ':' + this.end.toTex(); + str += ':' + this.end.toTex(callbacks); return str; }; diff --git a/lib/expression/node/SymbolNode.js b/lib/expression/node/SymbolNode.js index e6053d379..6d807d31d 100644 --- a/lib/expression/node/SymbolNode.js +++ b/lib/expression/node/SymbolNode.js @@ -99,11 +99,12 @@ SymbolNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str * @override */ -SymbolNode.prototype.toTex = function() { - return latex.toSymbol(this.name); +SymbolNode.prototype._toTex = function(callbacks) { + return latex.toSymbol(this.name, callbacks); }; module.exports = SymbolNode; diff --git a/lib/expression/node/UpdateNode.js b/lib/expression/node/UpdateNode.js index 523019d9e..323ad1f12 100644 --- a/lib/expression/node/UpdateNode.js +++ b/lib/expression/node/UpdateNode.js @@ -84,10 +84,11 @@ UpdateNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} */ -UpdateNode.prototype.toTex = function() { - return this.index.toTex() + ' = ' + this.expr.toTex(); +UpdateNode.prototype._toTex = function(callbacks) { + return this.index.toTex(callbacks) + ' = ' + this.expr.toTex(callbacks); }; module.exports = UpdateNode; diff --git a/lib/util/latex.js b/lib/util/latex.js index 176479d59..ed6afd33d 100644 --- a/lib/util/latex.js +++ b/lib/util/latex.js @@ -286,7 +286,7 @@ exports.addBraces = function(s, brace, type) { return braces[0] + s + braces[1]; }; -exports.toArgs = function(that) { +exports.toArgs = function(that, customFunctions) { var name = that.name, args = that.args, func = exports.toSymbol(that.name), @@ -358,13 +358,13 @@ exports.toArgs = function(that) { op = '!'; } else { - return '{\\left(' + args[0].toTex() + '\\right)!}'; + return '{\\left(' + args[0].toTex(customFunctions) + '\\right)!}'; } } else { // op = 'P'; - var n = args[0].toTex(), - k = args[1].toTex(); + var n = args[0].toTex(customFunctions), + k = args[1].toTex(customFunctions); return '\\frac{' + n + '!}{\\left(' + n + ' - ' + k + '\\right)!}'; } break; @@ -385,7 +385,7 @@ exports.toArgs = function(that) { type = 'lr'; if (args.length === 2) { - var tmp = args[1].toTex(); + var tmp = args[1].toTex(customFunctions); if (tmp === '\\text{inf}') { tmp = '\\infty'; @@ -417,7 +417,7 @@ exports.toArgs = function(that) { type = 'lr'; if (args.length === 2) { - suffix = '_' + exports.addBraces(args[1].toTex()); + suffix = '_' + exports.addBraces(args[1].toTex(customFunctions)); args = [args[0]]; } break; @@ -437,7 +437,7 @@ exports.toArgs = function(that) { case 'log': var base = 'e'; if (args.length === 2) { - base = args[1].toTex(); + base = args[1].toTex(customFunctions); func = '\\log_{' + base + '}'; args = [args[0]]; } @@ -466,7 +466,11 @@ exports.toArgs = function(that) { case 'det': if (that.args[0] instanceof ArrayNode) { - return that.args[0].toTex('vmatrix'); + //FIXME passing 'vmatrix' like that is really ugly + that.args[0].latexType = 'vmatrix'; + var latex = that.args[0].toTex(customFunctions); + delete that.args[0].latexType; + return latex; } brace = 'vmatrix'; @@ -480,7 +484,7 @@ exports.toArgs = function(that) { if (op !== null) { brace = (op === '+' || op === '-'); - texParams = (new OperatorNode(op, name, args)).toTex(); + texParams = (new OperatorNode(op, name, args)).toTex(customFunctions); } else { op = ', '; @@ -491,7 +495,7 @@ exports.toArgs = function(that) { } texParams = texParams || args.map(function(param) { - return '{' + param.toTex() + '}' ; + return '{' + param.toTex(customFunctions) + '}' ; }).join(op); return prefix + (showFunc ? func : '') + diff --git a/test/expression/node/ArrayNode.test.js b/test/expression/node/ArrayNode.test.js index 56259354c..fcfd7e978 100644 --- a/test/expression/node/ArrayNode.test.js +++ b/test/expression/node/ArrayNode.test.js @@ -236,4 +236,29 @@ describe('ArrayNode', function() { assert.equal(n.toTex(), '\\begin{bmatrix}1&2\\\\3&4\\\\\\end{bmatrix}'); }); + it ('should LaTeX an ArrayNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunction = function (node, callback) { + if (node.type === 'ArrayNode') { + var latex = '\\left['; + node.nodes.forEach(function (node) { + latex += node.toTex(callback) + ', '; + }); + + latex += '\\right]'; + return latex; + } + else if (node.type === 'ConstantNode') { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + + var n = new ArrayNode([a, b]); + + assert.equal(n.toTex(customFunction), '\\left[const\\left(1, number\\right), const\\left(2, number\\right), \\right]'); + }); + }); diff --git a/test/expression/node/AssignmentNode.test.js b/test/expression/node/AssignmentNode.test.js index 8c555081d..86cb862dd 100644 --- a/test/expression/node/AssignmentNode.test.js +++ b/test/expression/node/AssignmentNode.test.js @@ -225,4 +225,22 @@ describe('AssignmentNode', function() { assert.equal(n.toTex(), '{b}=\\left({{a}={2}}\\right)'); }); + it ('should LaTeX an AssignmentNode with custom toTex', function () { + //Also checks if custom funcions get passed to the children + var customFunction = function (node, callback) { + if (node.type === 'AssignmentNode') { + return node.name + '\\mbox{equals}' + node.expr.toTex(callback); + } + else if (node.type === 'ConstantNode') { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + + var n = new AssignmentNode('a', a); + + assert.equal(n.toTex(customFunction), 'a\\mbox{equals}const\\left(1, number\\right)'); + }); + }); diff --git a/test/expression/node/BlockNode.test.js b/test/expression/node/BlockNode.test.js index db8d66f6e..8514fb00c 100644 --- a/test/expression/node/BlockNode.test.js +++ b/test/expression/node/BlockNode.test.js @@ -256,4 +256,28 @@ describe('BlockNode', function() { assert.equal(n.toTex(), '5\n{foo}={3};\nfoo'); }); + it ('should LaTeX a BlockNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunction = function (node, callback) { + if (node.type === 'BlockNode') { + var latex = ''; + node.blocks.forEach(function (block) { + latex += block.node.toTex(callback) + '; '; + }); + + return latex; + } + else if (node.type === 'ConstantNode') { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + + var n = new BlockNode([{node: a}, {node: b}]); + + assert.equal(n.toTex(customFunction), 'const\\left(1, number\\right); const\\left(2, number\\right); '); + }); + }); diff --git a/test/expression/node/ConditionalNode.test.js b/test/expression/node/ConditionalNode.test.js index e50ab1305..5b4001cec 100644 --- a/test/expression/node/ConditionalNode.test.js +++ b/test/expression/node/ConditionalNode.test.js @@ -260,4 +260,26 @@ describe('ConditionalNode', function() { assert.equal(n.toTex(), '\\left\\{\\begin{array}{l l}{{a}={2}}, &\\quad{\\text{if}\\;true}\\\\{{b}={3}}, &\\quad{\\text{otherwise}}\\end{array}\\right.'); }); + it ('should LaTeX a ConditionalNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunction = function (node, callback) { + if (node.type === 'ConditionalNode') { + return 'if ' + node.condition.toTex(callback) + + ' then ' + node.trueExpr.toTex(callback) + + ' else ' + node.falseExpr.toTex(callback); + } + else if (node.type === 'ConstantNode') { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + var c = new ConstantNode(3); + + var n = new ConditionalNode(a, b, c); + + assert.equal(n.toTex(customFunction), 'if const\\left(1, number\\right) then const\\left(2, number\\right) else const\\left(3, number\\right)'); + }); + }); diff --git a/test/expression/node/ConstantNode.test.js b/test/expression/node/ConstantNode.test.js index a287de12b..c572377d6 100644 --- a/test/expression/node/ConstantNode.test.js +++ b/test/expression/node/ConstantNode.test.js @@ -139,4 +139,17 @@ describe('ConstantNode', function() { assert.equal(new ConstantNode('null', 'null').toTex(), 'null'); }); + it ('should LaTeX a ConstantNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunction = function (node, callback) { + if (node.type === 'ConstantNode') { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var n = new ConstantNode(1); + + assert.equal(n.toTex(customFunction), 'const\\left(1, number\\right)'); + }); + }); diff --git a/test/expression/node/FunctionAssignmentNode.test.js b/test/expression/node/FunctionAssignmentNode.test.js index 9b6209ea7..a7d8cff62 100644 --- a/test/expression/node/FunctionAssignmentNode.test.js +++ b/test/expression/node/FunctionAssignmentNode.test.js @@ -206,4 +206,29 @@ describe('FunctionAssignmentNode', function() { assert.equal(n.toTex(), 'f\\left({x}\\right)=\\left({{a}={2}}\\right)'); }); + + it ('should LaTeX a FunctionAssignmentNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunction = function (node, callback) { + if (node.type === 'FunctionAssignmentNode') { + var latex = '\\mbox{' + node.name + '}\\left('; + node.params.forEach(function (param) { + latex += param + ', '; + }); + + latex += '\\right)=' + node.expr.toTex(callback); + return latex; + } + else if (node.type === 'ConstantNode') { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + + var n = new FunctionAssignmentNode('func', ['x'], a); + + assert.equal(n.toTex(customFunction), '\\mbox{func}\\left(x, \\right)=const\\left(1, number\\right)'); + }); + }); diff --git a/test/expression/node/FunctionNode.test.js b/test/expression/node/FunctionNode.test.js index d5e743746..df3a98882 100644 --- a/test/expression/node/FunctionNode.test.js +++ b/test/expression/node/FunctionNode.test.js @@ -288,4 +288,48 @@ describe('FunctionNode', function() { assert.equal(n.getIdentifier(), 'FunctionNode:factorial'); }); + it ('should LaTeX a FunctionNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunction = function (node, callback) { + if (node.type === 'FunctionNode') { + var latex = '\\mbox{' + node.name + '}\\left('; + node.args.forEach(function (arg) { + latex += arg.toTex(callback) + ', '; + }); + latex += '\\right)'; + return latex; + } + else if (node.type === 'ConstantNode') { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + + var n1 = new FunctionNode('add', [a, b]); + var n2 = new FunctionNode('subtract', [a, b]); + + assert.equal(n1.toTex(customFunction), '\\mbox{add}\\left(const\\left(1, number\\right), const\\left(2, number\\right), \\right)'); + assert.equal(n2.toTex(customFunction), '\\mbox{subtract}\\left(const\\left(1, number\\right), const\\left(2, number\\right), \\right)'); + }); + + it ('should LaTeX a FunctionNode with custom toTex for a single function', function () { + //Also checks if the custom functions get passed on to the children + var customFunction = { + 'add': function (node, callbacks) { + return node.args[0].toTex(callbacks) + + ' ' + node.name + ' ' + + node.args[1].toTex(callbacks); + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + + var n = new FunctionNode('add', [a, b]); + + assert.equal(n.toTex(customFunction), '1 add 2'); + }); + }); diff --git a/test/expression/node/IndexNode.test.js b/test/expression/node/IndexNode.test.js index dc041b3e9..149d16bc6 100644 --- a/test/expression/node/IndexNode.test.js +++ b/test/expression/node/IndexNode.test.js @@ -285,4 +285,29 @@ describe('IndexNode', function() { assert.equal(n2.toTex(), 'a[]') }); + it ('should LaTeX an IndexNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunction = function (node, callback) { + if (node.type === 'IndexNode') { + var latex = node.object.toTex(callback) + ' at '; + node.ranges.forEach(function (range) { + latex += range.toTex(callback) + ', '; + }); + + return latex; + } + else if (node.type === 'ConstantNode') { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new SymbolNode('a'); + var b = new ConstantNode(1); + var c = new ConstantNode(2); + + var n = new IndexNode(a, [b, c]); + + assert.equal(n.toTex(customFunction), 'a at const\\left(1, number\\right), const\\left(2, number\\right), '); + }); + }); diff --git a/test/expression/node/Node.test.js b/test/expression/node/Node.test.js index 355856d16..18868968b 100644 --- a/test/expression/node/Node.test.js +++ b/test/expression/node/Node.test.js @@ -74,9 +74,29 @@ describe('Node', function() { assert.equal(node.toString(), ''); }); - it ('should LaTeX a Node', function () { - var node = new Node(); - assert.equal(node.toTex(), ''); + it ('should throw an error when calling _toTex', function () { + assert.throws(function () { + var node = new Node(); + node.type = 'SpecialNode'; //this is necessary because toTex + //returns '' for a Node + node._toTex(); + }, /_toTex not implemented for this Node/); + }); + + it ('should ignore custom toTex if it returns nothing', function () { + var callback1 = function (node, callback) {}; + var callback2 = { + bla: function (node, callbacks) {} + }; + var mymath = math.create(); + mymath.expression.node.Node.prototype._toTex = function () { + return 'default'; + }; + var n1 = new mymath.expression.node.Node(); + var n2 = new mymath.expression.node.FunctionNode('bla', []); + + assert.equal(n1.toTex(callback1), 'default'); + assert.equal(n2.toTex(callback2), 'bla\\left({}\\right)'); }); it ('should throw an error in case of wrong arguments for compile', function () { diff --git a/test/expression/node/OperatorNode.test.js b/test/expression/node/OperatorNode.test.js index 07ded28f0..bd58ff669 100644 --- a/test/expression/node/OperatorNode.test.js +++ b/test/expression/node/OperatorNode.test.js @@ -378,4 +378,48 @@ describe('OperatorNode', function() { assert.equal(n.getIdentifier(), 'OperatorNode:add'); }); + it ('should LaTeX an OperatorNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunction = function (node, callback) { + if (node.type === 'OperatorNode') { + return node.op + node.fn + '(' + + node.args[0].toTex(callback) + + ', ' + node.args[1].toTex(callback) + ')'; + } + else if (node.type === 'ConstantNode') { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + + var n1 = new OperatorNode('+', 'add', [a, b]); + var n2 = new OperatorNode('-', 'subtract', [a, b]); + + assert.equal(n1.toTex(customFunction), '+add(const\\left(1, number\\right), const\\left(2, number\\right))'); + assert.equal(n2.toTex(customFunction), '-subtract(const\\left(1, number\\right), const\\left(2, number\\right))'); + }); + + it ('should LaTeX an OperatorNode with custom toTex for a single operator', function () { + //Also checks if the custom functions get passed on to the children + var customFunction = function (node, callback) { + if ((node.type === 'OperatorNode') && (node.fn === 'add')) { + return node.args[0].toTex(callback) + + node.op + node.fn + node.op + + node.args[1].toTex(callback); + } + else if (node.type === 'ConstantNode') { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + + var n = new OperatorNode('+', 'add', [a, b]); + + assert.equal(n.toTex(customFunction), 'const\\left(1, number\\right)+add+const\\left(2, number\\right)'); + }); + }); diff --git a/test/expression/node/RangeNode.test.js b/test/expression/node/RangeNode.test.js index a69c529cd..ae9ab51b4 100644 --- a/test/expression/node/RangeNode.test.js +++ b/test/expression/node/RangeNode.test.js @@ -289,4 +289,26 @@ describe('RangeNode', function() { assert.equal(n.toTex(), '0:2:10'); }); + it ('should LaTeX a RangeNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunction = function (node, callback) { + if (node.type === 'RangeNode') { + return 'from ' + node.start.toTex(callback) + + ' to ' + node.end.toTex(callback) + + ' with steps of ' + node.step.toTex(callback); + } + else if (node.type === 'ConstantNode') { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + var c = new ConstantNode(3); + + var n = new RangeNode(a, b, c); + + assert.equal(n.toTex(customFunction), 'from const\\left(1, number\\right) to const\\left(2, number\\right) with steps of const\\left(3, number\\right)'); + }); + }); diff --git a/test/expression/node/SymbolNode.test.js b/test/expression/node/SymbolNode.test.js index 33932fbd2..7152acf29 100644 --- a/test/expression/node/SymbolNode.test.js +++ b/test/expression/node/SymbolNode.test.js @@ -107,4 +107,17 @@ describe('SymbolNode', function() { assert.equal(s.toTex(), 'foo'); }); + it ('should LaTeX a SymbolNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunction = function (node, callback) { + if (node.type === 'SymbolNode') { + return 'symbol(' + node.name + ')'; + } + }; + + var n = new SymbolNode('a'); + + assert.equal(n.toTex(customFunction), 'symbol(a)'); + }); + }); diff --git a/test/expression/node/UpdateNode.test.js b/test/expression/node/UpdateNode.test.js index 402029f84..543ef1ae7 100644 --- a/test/expression/node/UpdateNode.test.js +++ b/test/expression/node/UpdateNode.test.js @@ -322,4 +322,34 @@ describe('UpdateNode', function() { assert.equal(n.toString(), 'a[2, 1] = 5'); }); + it ('should LaTeX an UpdateNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunction = function (node, callback) { + if (node.type === 'UpdateNode') { + return node.index.toTex(callback) + ' equals ' + node.expr.toTex(callback); + } + else if (node.type === 'IndexNode') { + var latex = node.object.toTex(callback) + ' at '; + node.ranges.forEach(function (range) { + latex += range.toTex(callback) + ', '; + }); + return latex; + } + else if (node.type === 'ConstantNode') { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new SymbolNode('a'); + var ranges = [ + new ConstantNode(2), + new ConstantNode(1) + ]; + var v = new ConstantNode(5); + + var n = new UpdateNode(new IndexNode(a, ranges), v); + + assert.equal(n.toTex(customFunction), 'a at const\\left(2, number\\right), const\\left(1, number\\right), equals const\\left(5, number\\right)'); + }); + });