Merge pull request #617 from FSMaxB/implicit-mult-totex

Options for `toTex` and `toString` output of implicit multiplications
This commit is contained in:
Jos de Jong 2016-03-19 20:22:21 +01:00
commit f67df0fbfe
9 changed files with 145 additions and 11 deletions

View File

@ -197,7 +197,8 @@ The functions `toTex` and `toString` accept an `options` argument to customise o
```js
{
parenthesis: 'keep', // parenthesis option
handler: someHandler // handler to change the output
handler: someHandler, // handler to change the output
implicit: 'hide' // how to treat implicit multiplication
}
```
@ -325,3 +326,20 @@ var expression = math.parse('binomial(2,1)');
var latex = expression.toTex({handler: customLaTeX});
//latex now contains "\binom{2}{1}"
```
### Implicit multiplication
You can change the way that implicit multiplication is converted to a string or LaTeX. The two options are `hide`, to not show a multiplication operator for implicit multiplication and `show` to show it.
Example:
```js
var node = math.parse('2a');
node.toString(); //'2 a'
node.toString({implicit: 'hide'}); //'2 a'
node.toString({implicit: 'show'}); //'2 * a'
node.toTex(); //'2~ a'
node.toTex({implicit: 'hide'}); //'2~ a'
node.toTex({implicit: 'show'}); //'2\\cdot a'
```

View File

@ -65,13 +65,18 @@
<input type="radio" name="parenthesis" value="keep" onclick="parenthesis = 'keep'; expr.oninput();" checked>keep
<input type="radio" name="parenthesis" value="auto" onclick="parenthesis = 'auto'; expr.oninput();">auto
<input type="radio" name="parenthesis" value="all" onclick="parenthesis = 'all'; expr.oninput();">all
<br/>
<b>Implicit multiplication:</b>
<input type="radio" name="implicit" value="hide" onclick="implicit = 'hide'; expr.oninput();" checked>hide
<input type="radio" name="implicit" value="show" onclick="implicit = 'show'; expr.oninput();">show
<script>
var expr = document.getElementById('expr'),
pretty = document.getElementById('pretty'),
result = document.getElementById('result'),
parenthesis = 'keep';
parenthesis = 'keep',
implicit = 'hide';
// initialize with an example expression
expr.value = 'sqrt(75 / 3) + det([[-1, 2], [3, 1]]) - sin(pi / 4)^2';
@ -94,7 +99,7 @@
try {
// export the expression to LaTeX
var latex = node ? node.toTex({parenthesis: parenthesis}) : '';
var latex = node ? node.toTex({parenthesis: parenthesis, implicit: implicit}) : '';
console.log('LaTeX expression:', latex);
// display and re-render the expression

View File

@ -17,8 +17,9 @@ function factory (type, config, load, typed, math) {
* @param {string} op Operator name, for example '+'
* @param {string} fn Function name, for example 'add'
* @param {Node[]} args Operator arguments
* @param {boolean} implicit Is this an implicit multiplication?
*/
function OperatorNode(op, fn, args) {
function OperatorNode(op, fn, args, implicit) {
if (!(this instanceof OperatorNode)) {
throw new SyntaxError('Constructor must be called with the new operator');
}
@ -35,6 +36,7 @@ function factory (type, config, load, typed, math) {
throw new TypeError('Array containing Nodes expected for parameter "args"');
}
this.implicit = (implicit === true);
this.op = op;
this.fn = fn;
this.args = args || [];
@ -279,6 +281,7 @@ function factory (type, config, load, typed, math) {
*/
OperatorNode.prototype._toString = function (options) {
var parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep';
var implicit = (options && options.implicit) ? options.implicit : 'hide';
var args = this.args;
var parens = calculateNecessaryParentheses(this, parenthesis, args, false);
@ -311,6 +314,10 @@ function factory (type, config, load, typed, math) {
rhs = '(' + rhs + ')';
}
if (this.implicit && (this.getIdentifier() === 'OperatorNode:multiply') && (implicit == 'hide')) {
return lhs + ' ' + rhs;
}
return lhs + ' ' + this.op + ' ' + rhs;
default:
@ -326,6 +333,7 @@ function factory (type, config, load, typed, math) {
*/
OperatorNode.prototype._toTex = function (options) {
var parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep';
var implicit = (options && options.implicit) ? options.implicit : 'hide';
var args = this.args;
var parens = calculateNecessaryParentheses(this, parenthesis, args, true);
var op = latex.operators[this.fn];
@ -384,6 +392,10 @@ function factory (type, config, load, typed, math) {
case 'OperatorNode:divide':
lhsTex = '\\left(' + lhsTex + '\\right)';
}
case 'OperatorNode:multiply':
if (this.implicit && (implicit === 'hide')) {
return lhsTex + '~' + rhsTex;
}
}
return lhsTex + op + rhsTex;

View File

@ -88,7 +88,7 @@ function factory (type, config, load, typed) {
* @override
*/
ParenthesisNode.prototype._toString = function(options) {
if ((!options) || (options && options.parenthesis === 'keep')) {
if ((!options) || (options && !options.parenthesis) || (options && options.parenthesis === 'keep')) {
return '(' + this.content.toString(options) + ')';
}
return this.content.toString(options);
@ -101,7 +101,7 @@ function factory (type, config, load, typed) {
* @override
*/
ParenthesisNode.prototype._toTex = function(options) {
if ((!options) || (options && options.parenthesis === 'keep')) {
if ((!options) || (options && !options.parenthesis) || (options && options.parenthesis === 'keep')) {
return '\\left(' + this.content.toTex(options) + '\\right)';
}
return this.content.toTex(options);

View File

@ -896,7 +896,7 @@ function factory (type, config, load, typed) {
// number: implicit multiplication like '(2+3)2'
// parenthesis: implicit multiplication like '2(3+4)', '2[1,2,3]'
last = parseUnary();
node = new OperatorNode('*', 'multiply', [node, last]);
node = new OperatorNode('*', 'multiply', [node, last], true /*implicit*/);
}
else {
break;

View File

@ -505,4 +505,89 @@ describe('OperatorNode', function() {
assert.equal(math.parse('1+(1+1)').toTex({parenthesis: 'auto'}), '1+1+1');
});
it ('should stringify implicit multiplications', function () {
var a = math.parse('4a');
var b = math.parse('4 a');
var c = math.parse('a b');
var d = math.parse('2a b');
var e = math.parse('a b c');
var f = math.parse('(2+3)a');
var g = math.parse('(2+3)2');
var h = math.parse('2(3+4)');
assert.equal(a.toString(), a.toString({implicit: 'hide'}));
assert.equal(a.toString({implicit: 'hide'}), '4 a');
assert.equal(a.toString({implicit: 'show'}), '4 * a');
assert.equal(b.toString(), b.toString({implicit: 'hide'}));
assert.equal(b.toString({implicit: 'hide'}), '4 a');
assert.equal(b.toString({implicit: 'show'}), '4 * a');
assert.equal(c.toString(), c.toString({implicit: 'hide'}));
assert.equal(c.toString({implicit: 'hide'}), 'a b');
assert.equal(c.toString({implicit: 'show'}), 'a * b');
assert.equal(d.toString(), d.toString({implicit: 'hide'}));
assert.equal(d.toString({implicit: 'hide'}), '2 a b');
assert.equal(d.toString({implicit: 'show'}), '2 * a * b');
assert.equal(e.toString(), e.toString({implicit: 'hide'}));
assert.equal(e.toString({implicit: 'hide'}), 'a b c');
assert.equal(e.toString({implicit: 'show'}), 'a * b * c');
assert.equal(f.toString(), f.toString({implicit: 'hide'}));
assert.equal(f.toString({implicit: 'hide'}), '(2 + 3) a');
assert.equal(f.toString({implicit: 'show'}), '(2 + 3) * a');
assert.equal(g.toString(), g.toString({implicit: 'hide'}));
assert.equal(g.toString({implicit: 'hide'}), '(2 + 3) 2');
assert.equal(g.toString({implicit: 'show'}), '(2 + 3) * 2');
assert.equal(h.toString(), h.toString({implicit: 'hide'}));
assert.equal(h.toString({implicit: 'hide'}), '2 (3 + 4)');
assert.equal(h.toString({implicit: 'show'}), '2 * (3 + 4)');
});
it ('should LaTeX implicit multiplications', function () {
var a = math.parse('4a');
var b = math.parse('4 a');
var c = math.parse('a b');
var d = math.parse('2a b');
var e = math.parse('a b c');
var f = math.parse('(2+3)a');
var g = math.parse('(2+3)2');
var h = math.parse('2(3+4)');
assert.equal(a.toTex(), a.toTex({implicit: 'hide'}));
assert.equal(a.toTex({implicit: 'hide'}), '4~ a');
assert.equal(a.toTex({implicit: 'show'}), '4\\cdot a');
assert.equal(b.toTex(), b.toTex({implicit: 'hide'}));
assert.equal(b.toTex({implicit: 'hide'}), '4~ a');
assert.equal(b.toTex({implicit: 'show'}), '4\\cdot a');
assert.equal(c.toTex(), c.toTex({implicit: 'hide'}));
assert.equal(c.toTex({implicit: 'hide'}), ' a~\\mathrm{b}');
assert.equal(c.toTex({implicit: 'show'}), ' a\\cdot\\mathrm{b}');
assert.equal(d.toTex(), d.toTex({implicit: 'hide'}));
assert.equal(d.toTex({implicit: 'hide'}), '2~ a~\\mathrm{b}');
assert.equal(d.toTex({implicit: 'show'}), '2\\cdot a\\cdot\\mathrm{b}');
assert.equal(e.toTex(), e.toTex({implicit: 'hide'}));
assert.equal(e.toTex({implicit: 'hide'}), ' a~\\mathrm{b}~ c');
assert.equal(e.toTex({implicit: 'show'}), ' a\\cdot\\mathrm{b}\\cdot c');
assert.equal(f.toTex(), f.toTex({implicit: 'hide'}));
assert.equal(f.toTex({implicit: 'hide'}), '\\left(2+3\\right)~ a');
assert.equal(f.toTex({implicit: 'show'}), '\\left(2+3\\right)\\cdot a');
assert.equal(g.toTex(), g.toTex({implicit: 'hide'}));
assert.equal(g.toTex({implicit: 'hide'}), '\\left(2+3\\right)~2');
assert.equal(g.toTex({implicit: 'show'}), '\\left(2+3\\right)\\cdot2');
assert.equal(h.toTex(), h.toTex({implicit: 'hide'}));
assert.equal(h.toTex({implicit: 'hide'}), '2~\\left(3+4\\right)');
assert.equal(h.toTex({implicit: 'show'}), '2\\cdot\\left(3+4\\right)');
});
});

View File

@ -129,6 +129,7 @@ describe('ParenthesisNode', function() {
var n = new ParenthesisNode(a);
assert.equal(n.toString(), '(1)');
assert.equal(n.toString({}), '(1)');
});
it ('should stringify a ParenthesisNode when not in keep mode', function () {
@ -158,6 +159,7 @@ describe('ParenthesisNode', function() {
var n = new ParenthesisNode(a);
assert.equal(n.toTex(), '\\left(1\\right)');
assert.equal(n.toTex({}), '\\left(1\\right)');
});
it ('should LaTeX a ParenthesisNode when not in keep mode', function () {

View File

@ -1084,9 +1084,21 @@ describe('parse', function() {
assert.deepEqual(parseAndEval('[1,2;3,4] [2,2]', {A: [[1,2], [3,4]]}), 4); // index, no multiplication
});
it('should tell the OperatorNode about implicit multiplications', function() {
assert.equal(parse('4a').implicit, true);
assert.equal(parse('4 a').implicit, true);
assert.equal(parse('a b').implicit, true);
assert.equal(parse('2a b').implicit, true);
assert.equal(parse('a b c').implicit, true);
assert.equal(parse('(2+3)a').implicit, true);
assert.equal(parse('(2+3)2').implicit, true);
assert.equal(parse('2(3+4)').implicit, true);
});
it('should correctly order consecutive multiplications and implicit multiplications', function() {
var node = parse('9km*3km');
assert.equal(node.toString({parenthesis: 'all'}), '((9 * km) * 3) * km');
assert.equal(node.toString({parenthesis: 'all'}), '((9 km) * 3) km');
});
it('should throw an error when having an implicit multiplication between two numbers', function() {
@ -1849,8 +1861,8 @@ describe('parse', function() {
it('should correctly stringify a node tree', function() {
assert.equal(parse('0').toString(), '0');
assert.equal(parse('"hello"').toString(), '"hello"');
assert.equal(parse('[1, 2 + 3i, 4]').toString(), '[1, 2 + 3 * i, 4]');
assert.equal(parse('1/2a').toString(), '1 / 2 * a');
assert.equal(parse('[1, 2 + 3i, 4]').toString(), '[1, 2 + 3 i, 4]');
assert.equal(parse('1/2a').toString(), '1 / 2 a');
});
it('should correctly stringify an index with dot notation', function() {

View File

@ -83,6 +83,6 @@ describe('to', function() {
it('should LaTeX to', function () {
var expression = math.parse('to(2cm,m)');
assert.equal(expression.toTex(), '\\left(2\\cdot\\mathrm{cm}\\rightarrow\\mathrm{m}\\right)');
assert.equal(expression.toTex(), '\\left(2~\\mathrm{cm}\\rightarrow\\mathrm{m}\\right)');
});
});