mirror of
https://scm.univ-tours.fr/22107988t/rappaurio-sae501_502.git
synced 2025-08-29 22:05:57 +02:00
1129 lines
32 KiB
JavaScript
1129 lines
32 KiB
JavaScript
|
||
/**
|
||
* @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 中 <math>expression</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<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: ⁢ 用於表示乘法運算中被省略的乘號。
|
||
// 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: π
|
||
/(\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_
|
||
);
|
||
}
|
||
|
||
|
||
});
|
||
|