Files
rappaurio-sae501_502/app/node_modules/cejs/application/math.js
2023-09-25 13:27:24 +02:00

1129 lines
32 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @name CeL function for math application.
* @fileoverview
* 本檔案包含了生成 math application 的 functions。
* @since 2014/10/3
* @example
* <code>
* CeL.run('application.math', function() {
* // ..
* });
* </code>
*/
'use strict';
if (typeof CeL === 'function')
CeL.run(
{
name : 'application.math',
// includes() @ data.code.compatibility.
require : 'data.code.compatibility.|data.math.',
code : function module_code(library_namespace) {
/**
* null module constructor
* @class 出數學題目用的 functions
*/
var _// JSDT:_module_
= function() {
// null module constructor
};
/**
* for JSDT: 有 prototype 才會將之當作 Class
*/
_// JSDT:_module_
.prototype = {
};
//----------------------------------------------------- //
(function() {
runCode.setR = 0;
for (var i = 0, j, t, s, n_e; i < 10;) {
t = 2000 + 8000 * Math.random();
s = get_random_prime.get_different_number_set(3, t, t / 8);
if (s.LCM > 9999)
continue;
n_e = [];
n_e[s.GCD] = 1;
for (j = 0; j < s.length; j++)
if (n_e[s[j]])
continue;
else
n_e[s[j]] = 1;
sl([ s.GCD, s.LCM ] + '<b style="color:#c4a">;</b> ' + s);
i++;
}
});
//----------------------------------------------------- //
// 中式短除法(Chinese short division)並非 short division.
// https://en.wikipedia.org/wiki/Short_division
function draw_short_division(naturals, layer, GCD_only) {
if (!Array.isArray(naturals))
if (isNaN(layer))
naturals = [ naturals ];
else
naturals = arguments, layer = null;
var i, length = naturals.length | 0, divisor, cell_width_em = 2,
//
natural_Array = [];
for (i = 0; i < length; i++) {
divisor = +naturals[i];
if (0 < divisor && divisor < Number.MAX_SAFE_INTEGER) {
natural_Array.push(divisor);
if (cell_width_em < String(divisor).length * .6)
cell_width_em = Math.ceil(String(divisor).length * .6);
}
}
length = (naturals = Array.prototype.slice.call(natural_Array)).length | 0;
var block = [], count = 0,
//
_GCD = library_namespace.GCD(natural_Array),
//
GCD = library_namespace.factorize(_GCD);
library_namespace.debug(length + ' Naturals: '+natural_Array+'.',2);
if (GCD) {
// assert: _GCD > 1
if (_GCD !== (divisor = GCD.toString(true)))
_GCD += ' = ' + divisor;
// phase 1: 處理 GCD 部分。
for (divisor in GCD) {
divisor |= 0;
for (var j = 0; j < GCD[divisor] | 0; j++)
if (length !== 1 || natural_Array[0] !== divisor) {
block.push(draw_short_division.add_line(natural_Array, cell_width_em, divisor, count++, true));
for (i = 0; i < length; i++)
natural_Array[i] = natural_Array[i] / divisor;
}
}
}
// phase 2: 處理 LCM 部分。
// TODO: 按大小排列。
GCD = 0;
do {
var LCM = natural_Array[0];
for (i = 1; i < length; i++)
if (natural_Array[i] > 1 && (GCD = library_namespace.GCD(LCM, natural_Array[i])) > 1) {
divisor = +library_namespace.first_factor(GCD);
block.push(draw_short_division.add_line(natural_Array, cell_width_em, divisor, count++));
for (i = 0; i < length; i++)
if (natural_Array[i] % divisor === 0)
natural_Array[i] = natural_Array[i] / divisor;
break;
}
} while (GCD > 1);
// 依照各種不同類之輸入,顯示不同備註標示。
if (length === 1) {
// 質因數分解。
block.push(
draw_short_division.add_line(natural_Array, cell_width_em),
{
div: [
_GCD
]
}
);
} else {
i = library_namespace.LCM(naturals);
block.push(
draw_short_division.add_line(natural_Array, cell_width_em),
{
div: [
'GCD',
// '(', naturals.join(', '), ')',
' = ',
_GCD
],
S: draw_short_division.GCD_style
}, {
div: [
'LCM',
// '(', naturals.join(', '), ')',
' = ',
i,
' = ',
library_namespace.factorize(i).toString(true)
]
}
);
}
// 最後收尾。
block = {
div: block,
style: 'width:' + (1 + (length + (1 < length ? 2 : 1)) * cell_width_em | 0) + 'em;background-color:#def',
C: 'short_division'
};
return layer ? new_node(block, layer) : block;
}
draw_short_division.GCD_style = 'color:#f79;';
draw_short_division.add_line = function(naturals, cell_width_em, divisor, count, phase_GCD) {
library_namespace.debug(divisor + ': ' + naturals, 3, 'draw_short_division.add_line');
var line = [];
naturals.forEach(function(natural, index) {
line.push({
span: natural,
S: 'display:inline-block;text-align:right;padding-right:.2em;width:'
+ (cell_width_em + (index ? 0 : .5 - count / 5)).to_fixed(2) + 'em'
}, ' ');
});
line.pop();
if (divisor)
line = [{
span: divisor,
S: 'text-align:right;padding-right:.2em;'
}, {
span: line,
S: 'border-left:1pt solid #88f;border-bottom:1pt solid #88f'
}];
return {
div: line,
S: 'clear:both;text-align:right;' + (phase_GCD ? draw_short_division.GCD_style : '')
};
};
_.draw_short_division = draw_short_division;
/*
CeL.run('application.math', function() {
CeL.draw_short_division([12], [ document.body, 2 ]);
CeL.draw_short_division([12, 18], [ document.body, 2 ]);
CeL.draw_short_division([12, 18, 24], [ document.body, 2 ]);
});
*/
// ---------------------------------------------------------------------------------------------- //
var new_node = function () {
var func = library_namespace.DOM.new_node;
if (func)
return (new_node = func).apply(null, arguments);
},
//
check_MathML = function() {
var math_node = new_node({
div : {
span : 'normal'
},
S : 'line-height:1em;visibility:hidden'
}, document.body);
if (!math_node)
return;
new_node({
math : {
mfrac : [ {
mi : 'test'
}, {
mi : 'MathML'
} ]
}
}, math_node, 'mathml');
// Firefox/37.0 不需要 setTimeout()。
// setTimeout(check_MathML.check.bind(math_node), 0);
// return check_MathML.check.call(math_node, true);
return check_MathML.check.call(math_node);
};
// 2015/1/1: Firefox only. 僅 firefox 回傳 true。
check_MathML.check = function(no_remove) {
// library_namespace.debug(this);
Object.defineProperty(_, 'support_MathML', {
// method 1:
// 分數 2/3 之 2 的 offsetTop 應該比 3 更高一點。
// 但在 ff 中,沒有 .offsetTop。
// var mfrac = this.lastChild.firstChild;
// value : mfrac.firstChild.offsetTop < mfrac.lastChild.offsetTop;
// method 2: 因為插入 <mfrac><div> 應該比一般單行文字更高一點。但在 Chrome兩者本身即有差別。
value : this.offsetHeight > this.firstChild.offsetHeight + 3
});
if (!_.support_MathML)
library_namespace.debug('The browser does not support MathML. 您的瀏覽器不支援 MathML或是 MathML 功能已被關閉。');
if (no_remove !== true) {
// library_namespace.remove_node(this);
document.body.removeChild(this);
}
return _.support_MathML;
};
// assert: support MathML 也必定 support Object.defineProperty().
Object.defineProperty(_, 'support_MathML', {
configurable : true,
get : check_MathML
});
// ----------------------------------------------------- //
/**
* 以 MathML 表現數學運算式。<br />
* 將 HTML 中 &lt;math>expression&lt;/math> 皆轉為 MathML。<br />
* parse math expression & output MathML.<br />
*
* TODO: calculator, vector, , ∈∉
*
* @example <code>
// <script>
CeL.run([
// for new_node()
'interact.DOM', 'application.math' ], function() {
CeL.application.math.convert_MathML();
});
// <html>
<math>α=r×β</math><hr />
<math>5%*6=30%</math><hr />
<math>3*5/7</math><hr />
<math>3*(5/7)</math><hr />
<math>(4+5)/2</math><hr />
<math>5^4*3+2-4^2/5</math><hr />
<math>(a/b)/(c/d)</math><hr />
<math>資本收入=資本收益率×資本</math><hr />
<math>資本收入/國民年收入=資本收益率×(資本存量/國民年收入)</math><hr />
<math>
1/2+(1+2)/(2+3%)+((2+3))/((3+4))
</math><hr />
<math>
43+(54+5*(3+4)/3)*2
</math><hr />
<math>x_2^4</math><hr />
<math>x^2+y_1+z_12^34</math><hr />
<math>
x_2^4 = 3/2, y = 4/3, z = 5/4
</math><hr />
<math>
x_2^4 = 3/2
y = 4/3
z = 5/4
</math><hr />
<math>4^(1/7)</math><hr />
<math>√453</math><hr />
<math>√4e</math><hr />
<math>√-4e</math><hr />
<math>√(4e+3)</math><hr />
<math>∛45</math><hr />
<math>x=1/√3</math><hr />
<math>3&lt;4</math><hr />
<math>log_4(45+2)</math><hr />
<math>log_4 452</math><hr />
<math>log_e 452</math><hr />
<math>sin(π/2) = sin(pi/2) = sin(90°)</math><hr />
<math>sin π/2 = (sin π)/2</math><hr />
<math>sin(34π) = sin 34π</math><hr />
<math>sin^-1(2π) = sin^-1 2π = sin^(-1)(2 pi)</math><hr />
<math>log_4^2 452</math><hr />
<math>log_4^2(34+2)</math><hr />
<math>2e+3^2π+4π</math><hr />
<math>-4e</math><hr />
<math>(5+3)(5-4)</math><hr />
<math>7^(1/4) , 4√7</math><hr />
<math>x_y^2 x^2+2^(1/3)^8+4^(1/7)^6</math><hr />
<math>1+(b^2-4a c)^(1/5)</math><hr />
<math>x=(-b±√(b^2-4a c))/2a</math><hr />
<math>y=x(x^2-1)</math><hr />
<math>(^2)Fe</math><hr />
<math>(^12)C</math><hr />
<math>(_6^12)C</math><hr />
<math>(_6)C</math><hr />
<math>(_2^1)x_4^3</math><hr />
<math>(_2^1)x_4</math><hr />
// TODO:
<math>{ a; b; c, d, e }</math><hr />
<math>{{4, 5, 6}, {7, 8, 9}, {1, 2, 3}}×((3,4,5),(5,6,7),(7,8,9))</math><hr />
<math>{{1,2},{3,4}}((5),(6))</math><hr />
<math>x_y^2 x^2 2^(1/3)^8 4^(1/7)^6</math><hr />
<math>(_(a+b))x</math><hr />
munderover
<math>∫_2^4 dy/dx</math><hr />
// reference
http://www.w3.org/TR/MathML/chapter3.html#id.3.1.3.2
https://developer.mozilla.org/en-US/docs/Web/MathML/Element
http://www.wolframalpha.com/examples/Math.html
http://reference.wolfram.com/language/ref/format/MathML.html
* </code>
*
* @see
*/
function convert_MathML(handler) {
if (!library_namespace.remove_all_child)
return;
// assert: library_namespace.DOM is loaded.
if (!_.support_MathML) {
library_namespace.warn('The browser does not support MathML!');
if (!library_namespace.is_WWW(true))
return;
}
// MathML nodes
var nodes = document.getElementsByTagName('math'), length = nodes.length, i = 0, node;
if (!handler)
handler = convert_MathML.handler['default'];
else if (handler in convert_MathML.handler)
handler = convert_MathML.handler[handler];
for (; i < length; i++) {
node = nodes[i];
if (!_.support_MathML) {
if (!node.title && node.getAttribute(convert_MathML.default_attribute)) {
// useless... Chrome 不會像 <span> 一般顯示 .title。
node.setAttribute('title', node.getAttribute(convert_MathML.default_attribute));
}
continue;
}
if (false && typeof node.getAttribute !== 'function')
return;
var text;
if (node.childNodes.length !== 1
|| node.firstChild.nodeType !== document.TEXT_NODE
//
|| !(text = node.firstChild.nodeValue.trim()))
continue;
// temporary usage.
var attribute = convert_MathML.default_attribute,
//
structure = node.getAttribute(attribute) || node.getAttribute(attribute = 'title');
if (structure) {
// 當可以使用 <math> 時,把原先展示的內容物 .childNodes 轉而擺到 .title 去。
// 使用例: <math alt="e^(-)" title="e-">電子</math>, <math alt="H_2 O" title="水 H2O">水分子</math>
// 避免覆蓋原先的 .title。.title 通常擺更詳細的資訊。
if (!node.getAttribute(attribute = 'title')) {
node.setAttribute('title', text);
}
text = '\n' + structure;
}
if (!node.getAttribute('xmlns')) {
node.setAttribute('xmlns', "http://www.w3.org/1998/Math/MathML");
}
// library_namespace.debug(node);
structure = convert_MathML.parse(text);
if (!structure[0]) {
// 若是所有 children 都是等式,則將之括弧起來。
var equalities = [ '{' ], j = 1, tmp;
for (; j < structure.length; j++) {
if (tmp = structure[j])
if (Array.isArray(tmp) && convert_MathML.RELATIONSHIP_PATTERN.test(tmp[0])) {
equalities.push(tmp);
} else if (typeof tmp !== 'string' || tmp.trim()) {
equalities = null;
break;
}
}
if (equalities)
structure = equalities;
}
structure = convert_MathML.reduce(structure, node, handler);
// library_namespace.debug('convert_MathML: structure:');
// library_namespace.debug(structure);
library_namespace.remove_all_child(node);
new_node(structure, node, 'mathml');
}
}
_.convert_MathML = convert_MathML;
convert_MathML.default_attribute = 'alt';
convert_MathML.reduce_quote = function(operand) {
var row = operand.mrow;
if (!row || !Array.isArray(row) || row.length !== 3) {
return operand;
}
var operator = row[0].mo + row[2].mo;
if (operator === '{}' || operator === '()') {
// assert: Array.isArray(row[1].mrow)
return row[1];
}
return operand;
};
convert_MathML.handler = {
// toString()
// 運算元,運算子,運算元(操作符/算符/算子)
string : function(operand_1, operator, operand_2) {
if (!operator && Array.isArray(operand_1))
return operand_1.join(' ');
switch (operator) {
case '()':
return '(' + operand_1 + ')';
case 'm':
// 以表格呈現。
operand_1.forEach(function(row, index) {
operand_1[index] = '{' + row.join(',') + '}';
});
case '{}':
return '{' + operand_1 + '}';
case ',':
return operand_1.join(',');
case '{':
return operand_1.join('\n');
}
return operand_1 + (operator || '') + (operand_2 || '');
},
mathml : function(operand_1, operator, operand_2) {
if (!operator && Array.isArray(operand_1))
return operand_1;
switch (operator) {
case '{}':
case '()':
if (typeof operand_1 !== 'object') {
operand_1 = convert_MathML.parse_scalar(operand_1);
} else if (!operand_1.mfrac && Array.isArray(operand_1)) {
operand_1 = [ {
mo : operator === '{}' ? '{' : '('
}, {
mrow : operand_1
}, {
mo : operator === '{}' ? '}' : ')'
} ];
} else if (false && !operand_1.mfrac) {
// for !operand_1.mfrac: "(1/2)" → "1/2"
operand_1 = {
// deprecated MathML <mfenced> element
mfenced : operand_1,
separators : ''
};
if (operator === '{}') {
operand_1.open = '{';
operand_1.close = '}';
}
}
return operand_1;
case 'm':
// 以表格呈現矩陣。
operand_1.forEach(function(row, index) {
row.forEach(function(expression, index) {
row[index] = {
mtd : expression
};
});
operand_1[index] = {
mtr : row
};
});
operand_1 = {
mfenced : {
mtable : operand_1
}
};
if (operand_2 !== '()')
operand_1.open = '[', operand_1.close = ']';
return operand_1;
case '{':
// 以表格呈現方程式組。
operand_1.forEach(function(equality, index) {
operand_1[index] = {
mtr : {
mtd : equality
}
};
});
return {
mfenced : {
mtable : operand_1
},
open : '{',
close : ''
};
case ',':
case ';':
operand_2 = [];
operand_1.forEach(function(expression) {
operand_2.push(convert_MathML.parse_scalar(expression), {
mo : operator
});
});
operand_2.pop();
return operand_2;
case '/':
case '':
// ↑ Fraction slash
case '':
// ↑ Division slash
operand_1 = convert_MathML.parse_scalar(operand_1);
operand_2 = convert_MathML.parse_scalar(operand_2);
// "(1)/(2)" → "1/(2)"
operand_1 = convert_MathML.reduce_quote(operand_1);
// "(1)/(2)" → "(1)/2"
operand_2 = convert_MathML.reduce_quote(operand_2);
if (false) {
if (operand_1.mfenced)
// "(1)/(2)" → "1/2"
operand_1 = convert_MathML.parse_scalar(operand_1.mfenced);
if (operand_2.mfenced)
// "(1)/(2)" → "1/2"
operand_2 = convert_MathML.parse_scalar(operand_2.mfenced);
}
// <mfrac> <mi>numerator</mi> <mi>denominator</mi> </mfrac>
operand_1 = {
mfrac : [ operand_1, operand_2 ]
};
// 除了這些外,皆當作分數,上下表示。
if (operator === '' || operator === '')
operand_1.bevelled = true;
return operand_1;
case '^':
operand_1 = convert_MathML.parse_scalar(operand_1);
operand_2 = convert_MathML.parse_scalar(operand_2);
if (operand_2.mfenced)
// 去除括號 "()"。
// "7^(2/3)" → "<msup>7 2/3</msup>"
operand_2 = convert_MathML.parse_scalar(operand_2.mfenced);
if (Array.isArray(operand_2.mfrac) && operand_2.mfrac[0].mn === 1) {
// 去除 operand_1 之括號 "()"。
if (operand_1.mfenced)
operand_1 = convert_MathML.parse_scalar(operand_1.mfenced);
// (operand_1) 的 (operand_2.mfrac[1]) 次方根。
// "7^(1/3)" → "<mroot> 7 3 </mroot>"
return {
mroot : operand_1 ? [ operand_1, operand_2.mfrac[1] ] : operand_2.mfrac[1]
};
}
return {
// 依照規定必須要有<mi>,不可以省略。 e.g., (^12)C
msup : [ operand_1 || {
none : null
}, operand_2 ]
};
case '√':
case '∛':
// 去除括號 "()"。
// "√(1+2)" → "<msqrt>1+2</msqrt>"
if (operand_1.mfenced)
operand_1 = operand_1.mfenced;
// (平)方根 / 立方根
return operator === '√' ? {
msqrt : operand_1
} : {
mroot : [ operand_1, {
mn : 3
} ]
};
}
operand_1 = convert_MathML.parse_scalar(operand_1);
if (!operator)
return operand_1;
operand_1 = [ operand_1, {
mo : operator
} ];
if (operand_2)
operand_1.push(convert_MathML.parse_scalar(operand_2));
return operand_1;
}
};
convert_MathML.handler['default'] = convert_MathML.handler.mathml;
// relationships, assignment, equalities
// https://en.wikipedia.org/wiki/Mathematical_operators_and_symbols_in_Unicode
// [≁-⊋]: ≁≂≃≄≅≆≇≈≉≊≋≌≍≎≏≐≑≒≓≔≕≖≗≘≙≚≛≜≝≞≟≠≡≢≣≤≥≦≧≨≩≪≫≬≭≮≯≰≱≲≳≴≵≶≷≸≹≺≻≼≽≾≿⊀⊁⊂⊃⊄⊅⊆⊇⊈⊉⊊⊋
// [⋀-⋭]: ⋀⋁⋂⋃⋄⋅⋆⋇⋈⋉⋊⋋⋌⋍⋎⋏⋐⋑⋒⋓⋔⋕⋖⋗⋘⋙⋚⋛⋜⋝⋞⋟⋠⋡⋢⋣⋤⋥⋦⋧⋨⋩⋪⋫⋬⋭
// TODO: "~"
convert_MathML.RELATIONSHIP_PATTERN = '=><∝≁-⊋⋀-⋭';
// https://en.wikipedia.org/wiki/Plus_and_minus_signs#Character_codes
convert_MathML.non_scalar_chars = '(){}^√∛*\\/⁄∕×⋅÷+\\-−±' + convert_MathML.RELATIONSHIP_PATTERN + ',;\r\n';
// operator : [ pattern, handler, object_to_add ]
// 運算子優先順序最高到最低
(convert_MathML.operator = [
// parentheses
[ /\(([^()]+)\)/, function($0, $1) {
return [ '()', $1 ];
} ], [ /{([^{}]+)}/, function($0, $1) {
return [ '{}', $1 ];
} ], [ /\[([^[]]+)\]/, function($0, $1) {
return [ '[]', $1 ];
} ],
// exponents.
[ /(\S*)\^([+\-−±]?\S+)/, function($0, $1, $2) {
// [ , base, power ]
return [ '^', $1, $2 ];
} ], [ /([√∛])([+\-−±]?\S+)/, function($0, $1, $2) {
// [ , base, power ]
return [ $1, $2 ];
} ],
// multiplication and fraction.
[ /(\S+)([*\/⁄∕×⋅÷])(\S+)/, function($0, $1, $2, $3) {
if ($2 === '*')
$2 = '⋅';
// [ , numerator, denominator ]
return [ $2, $1, $3 ];
} ],
// addition and subtraction
[ /(\S+)([+\-−±])(\S+)/, function($0, $1, $2, $3) {
return [ $2, $1, $3 ];
} ],
// relationships, assignment, equalities
[ new RegExp('(\\S+)([' + convert_MathML.RELATIONSHIP_PATTERN + '])(\\S+)'), function($0, $1, $2, $3) {
return [ $2, $1, $3 ];
} ],
// terms
[ /\S+(?:,\S+)+/, function($0) {
($0 = $0.split(',')).unshift(',');
return $0;
} ],
// terms
[ /\S+(?:;\S+)+/, function($0) {
($0 = $0.split(';')).unshift(';');
return $0;
} ] ])
//
.forEach(function(term) {
// 不可用 'g'! e.g., 2+3-4+5
term[0] = new RegExp(term[0].source.replace(/\\S/g, '[^'
+ convert_MathML.non_scalar_chars + ']'), '');
// library_namespace.debug(term[0]);
});
convert_MathML.RELATIONSHIP_PATTERN = new RegExp('^[' + convert_MathML.RELATIONSHIP_PATTERN + ']$');
convert_MathML.process = function(text, order, queue) {
library_namespace.debug('[' + text + '] (' + order + ')', 3, 'convert_MathML.process');
var changed, operator;
while (true) {
if (changed)
changed = false;
else if (!(operator = convert_MathML.operator[order++]))
break;
else {
library_namespace.debug('shift to ' + operator[0], 3, 'convert_MathML.process');
}
text = text.trim()
//
.replace(
operator[0],
function($0, $1, $2, $3) {
// return [ type, text1, text2 ]
var expression = operator[1]($0, $1, $2, $3);
if (!expression) {
library_namespace
.error("convert_MathML.process: Cannot parse: '"
+ $0 + "'");
return $0;
}
changed = true;
// next order
expression.forEach(function(term, index) {
if (index > 0)
expression[index] = convert_MathML.resolve(
convert_MathML.process(term, order, queue),
queue);
});
queue.push(expression);
return (// queue.separator +
queue.prefix
// - 1: get the real index
+ (queue.length - 1) + queue.postfix
// + queue.separator
);
});
library_namespace.debug('→ [' + text + ']', 3, 'convert_MathML.process');
}
library_namespace.debug('return [' + text + ']', 3, 'convert_MathML.process');
return text;
};
// parse math expression.
convert_MathML.parse = function(text, queue) {
if (!queue) {
queue = [];
// assert: NOT space or operator.
queue.prefix = '';
queue.postfix = '';
while (text.includes(queue.prefix))
// 維持 open/close quote 相同的長度。
queue.prefix += '', queue.postfix = '' + queue.postfix;
while (text.includes(queue.postfix))
queue.prefix += '', queue.postfix = '' + queue.postfix;
// queue.separator = queue.prefix + queue.postfix;
queue.pattern = '\\' + queue.prefix.split('').join('\\') + '(\\d+)'
//
+ '\\' + queue.postfix.split('').join('\\');
// [ , index ]
queue.index_pattern = new RegExp('^\\s*' + queue.pattern + '\\s*$');
// [ , index || '' ]
queue.pattern = new RegExp(queue.pattern + '|$', 'g');
}
// 前期處理。
// TODO: °º⁺⁻⁼ ⁰¹²³⁴⁵⁶⁷⁸⁹⁽⁾ ±♥´ ₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎ ₐₑₒₓₔ½ ⅓⅔ ¼¾ ⅕⅖⅗⅘ ⅙⅚ ⅛⅜⅝⅞
text = text.replace(/!=|<>/g, '≠').replace(/>=/g, '≥').replace(/<=/g, '≤').replace(/⅟/g, '1');
// TODO: &InvisibleTimes; 用於表示乘法運算中被省略的乘號。
// https://zh.wikipedia.org/wiki/%E6%95%B0%E5%AD%A6%E7%BD%AE%E6%A0%87%E8%AF%AD%E8%A8%80#Presentation_MathML
if (false)
text = text.replace(
// 處理數學常數。
// https://en.wikipedia.org/wiki/Mathematical_constant
// 3^2π → 3^(2⋅π)
// TODO: &pi;
/(\d+(?:\.\d+)?)\s*(pi|[a-z\u0370-\u03FF])([^\da-z\u0370-\u03FF]|$)/ig,
'($1⋅$2)$3');
text = convert_MathML.process(text, 0, queue);
// library_namespace.debug('convert_MathML.parse: [' + text + ']');
// library_namespace.debug('convert_MathML.parse: queue [' + queue + ']');
text = convert_MathML.resolve(text, queue);
// library_namespace.debug('convert_MathML.parse: return [' + text + ']');
// library_namespace.debug('convert_MathML.parse: queue [' + queue + ']');
return text;
};
// (?:[+\-−±]?\d+(?:\.\d+)?[°∘%%‰‱]?|pi|PI|Pi|[eiKπδφγλΩ∞ℵ])
var PATTERN_numeric = /[+\-−±]?\d+(?:\.\d+)?[°∘%%‰‱]?/;
// [ , 純數, 識別元 ]
convert_MathML.PATTERN_numeric_prefix = new RegExp('^(' + PATTERN_numeric.source + ')([^\d].*)?$' + '$');
convert_MathML.is_numeric_prefix = function(expression) {
return expression.match(convert_MathML.PATTERN_numeric_prefix);
};
convert_MathML.PATTERN_numeric = new RegExp('^' + PATTERN_numeric.source + '$');
// 傳回 {Boolean},說明運算式是否可做為數字來評估。
convert_MathML.is_numeric = function(expression) {
return convert_MathML.PATTERN_numeric.test(expression);
};
// 解開 queue index。
// for: number, queue index, or the combination.
convert_MathML.resolve = function(text, queue) {
// library_namespace.debug('convert_MathML.resolve: [' + text + ']');
// library_namespace.debug('convert_MathML.resolve: queue [' + queue.join(';')
// + ']');
//function is_index(token) { return token.startsWith(queue.prefix) && token.endsWith(queue.postfix); }
var matched = text.match(queue.index_pattern);
if (matched)
return queue[matched[1]];
if (convert_MathML.is_numeric(text))
return text;
library_namespace
.debug('convert_MathML.resolve: Parse combinated expression: ['
+ text + ']');
var array = [ null ], lastIndex = 0, changed, matched;
for (queue.pattern.lastIndex = 0;;) {
matched = queue.pattern.exec(text);
// 前導 text
var pre_text = null;
if (matched.index > lastIndex) {
pre_text = text.substring(lastIndex, matched.index);
if (/(?:^\s*[+\-−±]?|\s)\d+(?:\.\d+)?(?:\s|$)/.test(pre_text))
// e.g., "log 3.3"
changed = true, Array.prototype.push.apply(array, pre_text
.split(/\s+/));
else
array.push(pre_text);
}
lastIndex = queue.pattern.lastIndex;
// library_namespace.debug([ pre_text, matched[1], lastIndex ]);
if (matched[1])
changed = true, array.push(queue[matched[1]]);
else {
if (!changed && pre_text && convert_MathML.is_numeric(pre_text))
changed = true;
break;
}
}
// if (changed) library_namespace.debug(array);
return changed ? array : text;
};
// 處理純量與變數。
convert_MathML.parse_scalar = function(text, no_MathML) {
if (typeof text === 'object') {
return !no_MathML && Array.isArray(text) ? {
mrow : text
} : text;
}
if (no_MathML)
return text;
if (!(text = String(text).trim()))
return text;
if(/\s/.test(text)) {
text = text.split(/\s+/);
// 多項。 e.g., "2a 3b 4ac"
text.forEach(function(term, index) {
text[index] = convert_MathML.parse_scalar(term);
});
return text;
}
var is_numeric = convert_MathML.is_numeric(text);
// 純數。e.g., "1"
if (is_numeric)
return {
mn : text
};
// 純數 + 識別元。e.g., "2a", "3.3π"
if (is_numeric = convert_MathML.is_numeric_prefix(text))
return [{
mn : is_numeric[1]
}, convert_MathML.parse_scalar(is_numeric[2]) ];
// 下標。e.g., "log_2"
if (is_numeric = text.match(/^([^_]*)_([^_]+)$/))
// <msub><mi>x</mi><mi>y</mi></msub>
return {
// 依照規定必須要有<mi>,不可以省略。 e.g., (_6)C
msub : [ convert_MathML.parse_scalar(is_numeric[1]) || {
// TODO: should use <none />
none : null
}, convert_MathML.parse_scalar(is_numeric[2]) ]
};
if (library_namespace.is_debug() &&
// a-zA-Z: normal variable. 變量
// \u0370-\u03ff: mathematical constant. 數學常數/希臘字母變量. e.g., π
// \u2E80-\u30000: Unihan variable
!/^[a-zA-Z\u0370-\u03ff∞ℵ\u2E80-\u30000][a-zA-Z\u0370-\u03ff∞ℵ\u2E80-\u30000\d]*$/
.test(text))
library_namespace.error("convert_MathML.parse_scalar: Cannot parse: '"
+ text + "'");
// 純識別元。e.g., "x"
return {
mi : text
};
};
// 將 convert_MathML.parse() 之結果reduce 成所須的格式。
convert_MathML.reduce = function(structure, node, handler) {
function process_mprescripts(structure, postsuperscript) {
if (!structure[0]
&& Array.isArray(structure[1])
&& structure[1][0] === '()'
&& structure[2]
// e.g., (_2), (_2^1), (_(a+b)), (_(a+b)^(c+d))
&& (Array.isArray(matched = structure[1][1]) ? /^[^_]$/.test(matched[0]) && (!matched[1] || /^_/.test(matched[1])) : !matched || /^_/.test(matched))
) {
// <mprescripts />
// https://developer.mozilla.org/zh-TW/docs/Web/MathML/Element/mmultiscripts
// e.g., (_2^1)x, (_2^1)x_4^3, (_2^1)x_4^(3)
var mmultiscripts = convert_MathML.reduce(structure[2], node, handler);
if (!Array.isArray(mmultiscripts)) {
if (mmultiscripts && Array.isArray(mmultiscripts.msub)) {
mmultiscripts = mmultiscripts.msub;
mmultiscripts.push(postsuperscript && {
mi : postsuperscript
} || {
none : null
});
} else {
mmultiscripts = [ mmultiscripts, {
none : null
}, {
none : null
} ];
}
}
mmultiscripts.push({
mprescripts : null
});
structure = structure[1][1];
if (!Array.isArray(structure)) {
structure = convert_MathML.reduce(structure, node, handler);
if (structure && Array.isArray(structure.msub)) {
structure = [ , structure.msub[1], structure.msub[0] ];
}
}
// presubscript
matched = convert_MathML.reduce(structure[1], node, handler);
if (!matched) {
matched = {
none : null
};
} else if (Array.isArray(matched.msub) && matched.msub.length === 2 && ('none' in matched.msub[0])) {
matched = matched.msub[1];
}
mmultiscripts.push(
// presubscript
matched,
// presuperscript
convert_MathML.reduce(structure[2], node, handler));
return {
mmultiscripts : mmultiscripts
};
}
}
// library_namespace.debug(structure);
if (!Array.isArray(structure))
return handler(structure);
// structure = [ operator, operand_1, operand_2, .. ]
if ((structure[0] in {
'()' : true,
'{}' : true,
'[]' : true
})
// 矩陣。
&& structure.length === 2 && Array.isArray(structure[1])
&& structure[1][0] === ',') {
var matrix = [], i = 0, length = structure[1].length;
if (structure[1].every(function(operand, index) {
if (index === 0)
// pass the operator
return true;
// 確認 array 型態相同。
if (operand[0] !== structure[0])
return;
// 確認 operand 為 array。
if (Array.isArray(operand[1]) && operand[1][0] === ','
// 確認 array 大小皆同。
&& (!matrix.length || matrix[0].length === operand[1].length - 1)) {
// e.g., {{1,2},{3,4}}
var m = [];
operand[1].forEach(function(expression, index) {
if (index > 0)
m.push(convert_MathML.reduce(expression, node,
handler));
});
// assert: true === !![].push(0)
return matrix.push(m);
}
// 確認 operand 為 array。
if (typeof operand[1] !== 'object'
// 確認 array 大小皆同。
&& (!matrix.length || matrix[0].length === 1))
// e.g., ((5),(6))
return matrix.push([ operand[1] ]);
if (library_namespace.is_debug()) {
library_namespace.debug('array 型態' + (operand[0] === structure[0] ? '相同' : '不同'));
library_namespace.debug(Array.isArray(operand[1]) && operand[1][0] === ',' ? 'operand 為 array' : 'operand 為 ' + (Array.isArray(operand[1]) ? operand[1][0] : operand[1]));
if (matrix[0])
library_namespace.debug('array 大小: ' + matrix[0].length + ' != ' + (operand[1].length - 1));
}
}))
return handler(matrix, 'm', structure[0]);
}
// 前期處理。
var matched;
if (structure[0] === '^') {
if (matched = process_mprescripts(structure[1], structure[2])) {
return matched;
}
// e.g., sin^-1(2π)
if (Array.isArray(structure[2]) && structure[2].length === 3
&& !structure[2][0]) {
structure[2][1] = [ '^', structure[1], structure[2][1] ];
structure = structure[2];
}
// e.g., sin^-1 2π
if (typeof structure[2] === 'string'
&& (matched = structure[2]
.match(/^([+\-−±]?\d+(?:\.\d+)?)\s+(\S+)$/))) {
structure[2] = matched[1];
structure = [ , structure, matched[2] ];
}
} else if (matched = process_mprescripts(structure)) {
return matched;
}
// TODO: <munderover />, <math>∫_2^4 dy/dx</math>
structure.forEach(function(operand, index) {
if (index > 0)
structure[index] = convert_MathML.reduce(operand, node, handler);
});
return structure[0] && !(structure[0] in {
'{' : true,
',' : true,
';' : true
}) ? handler(structure[1], structure[0], structure[2]) : handler(structure
.slice(1), structure[0]);
};
return (
_// JSDT:_module_
);
}
});