/*
Copyright (C) 2014 Michigan State University Board of Trustees
The JavaScript code in this page is free software: you can
redistribute it and/or modify it under the terms of the GNU
General Public License (GNU GPL) as published by the Free Software
Foundation, either version 3 of the License, or (at your option)
any later version. The code is distributed WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
As additional permission under GNU GPL version 3 section 7, you
may distribute non-source (e.g., minimized or compacted) forms of
that code without the copy of the GNU GPL normally required by
section 4, provided you include this license notice and a URL
through which recipients can access the Corresponding Source.
*/
"use strict";
/**
* Parsed tree node. ENode.toMathML() contains the code for the transformation into MathML.
* @constructor
* @param {number} type - ENode.UNKNOWN | NAME | NUMBER | OPERATOR | FUNCTION | VECTOR | INTERVAL | SET | SUBSCRIPT
* @param {Operator} op - The operator
* @param {string} value - Node value as a string, null for type VECTOR
* @param {Array.<ENode>} children - The children nodes, only for types OPERATOR, FUNCTION, VECTOR, INTERVAL, SET, SUBSCRIPT
* @param {number} [interval_type] - ENode.NOT_AN_INTERVAL | OPEN_OPEN | OPEN_CLOSED | CLOSED_OPEN | CLOSED_CLOSED
*/
function ENode(type, op, value, children, interval_type) {
this.type = type;
this.op = op;
this.value = value;
this.children = children;
if (typeof interval_type == "undefined")
this.interval_type = ENode.NOT_AN_INTERVAL;
else
this.interval_type = interval_type;
}
ENode.UNKNOWN = 0;
ENode.NAME = 1;
ENode.NUMBER = 2;
ENode.OPERATOR = 3;
ENode.FUNCTION = 4;
ENode.VECTOR = 5;
ENode.INTERVAL = 6;
ENode.SET = 7;
ENode.SUBSCRIPT = 8;
ENode.COLORS = ["#E01010", "#0010FF", "#009000", "#FF00FF", "#00B0B0", "#F09000",
"#800080", "#F080A0", "#6090F0", "#902000", "#70A050", "#A07060",
"#5000FF", "#E06050", "#008080", "#808000"];
ENode.NOT_AN_INTERVAL = 0;
ENode.OPEN_OPEN = 1;
ENode.OPEN_CLOSED = 2;
ENode.CLOSED_OPEN = 3;
ENode.CLOSED_CLOSED = 4;
/**
* Returns the node as a string, for debug
* @returns {string}
*/
ENode.prototype.toString = function() {
var s = '(';
switch (this.type) {
case ENode.UNKNOWN:
s += 'UNKNOWN';
break;
case ENode.NAME:
s += 'NAME';
break;
case ENode.NUMBER:
s += 'NUMBER';
break;
case ENode.OPERATOR:
s += 'OPERATOR';
break;
case ENode.FUNCTION:
s += 'FUNCTION';
break;
case ENode.VECTOR:
s += 'VECTOR';
break;
case ENode.INTERVAL:
s += 'INTERVAL';
break;
case ENode.SET:
s += 'SET';
break;
case ENode.SUBSCRIPT:
s += 'SUBSCRIPT';
break;
}
if (this.op)
s += " '" + this.op.id + "'";
if (this.value)
s += " '" + this.value + "'";
if (this.children) {
s += ' [';
for (var i = 0; i < this.children.length; i++) {
s += this.children[i].toString();
if (i != this.children.length - 1)
s += ',';
}
s += ']';
}
if (this.interval_type) {
s += " " + this.interval_type;
}
s+= ')';
return s;
};
/**
* Returns the color for an identifier.
* @param {string} name
* @param {Object.<string, string>} hcolors - hash identifier->color
* @returns {string}
*/
ENode.prototype.getColorForIdentifier = function(name, context) {
var res = context.hcolors[name];
if (!res) {
var colors;
if (context.colors)
colors = context.colors;
else
colors = ENode.COLORS;
res = colors[Object.keys(context.hcolors).length % colors.length];
context.hcolors[name] = res;
}
return res;
}
/**
* Transforms this ENode into a MathML HTML DOM element.
* @param {Array.<string>} [colors] - optional override for ENode.COLORS
* @returns {Element}
*/
ENode.prototype.toMathML = function(colors) {
var context = { hcolors: {}, depth: 0 , colors: colors};
return(this._toMathML(context));
}
/**
* Transforms this ENode into a MathML HTML DOM element (internal function).
* @param {Object} context - display context
* @param {Object.<string, string>} context.hcolors - hash identifier->color
* @param {number} context.depth - Depth in parenthesis, used for coloring
* @param {Array.<string>} context.colors - optional override for ENode.COLORS
* @returns {Element}
*/
ENode.prototype._toMathML = function(context) {
var c0, c1, c2, c3, c4, i, j, el, par, mrow, mo, mtable, mfrac, msub, msup, mrow2;
if (this.children != null && this.children.length > 0)
c0 = this.children[0];
else
c0 = null;
if (this.children != null && this.children.length > 1)
c1 = this.children[1];
else
c1 = null;
if (this.children != null && this.children.length > 2)
c2 = this.children[2];
else
c2 = null;
if (this.children != null && this.children.length > 3)
c3 = this.children[3];
else
c3 = null;
if (this.children != null && this.children.length > 4)
c4 = this.children[4];
else
c4 = null;
switch (this.type) {
case ENode.UNKNOWN:
el = document.createElement('mtext');
el.appendChild(document.createTextNode("???"));
return(el);
case ENode.NAME:
if (this.value.search(/^[a-zA-Z]+[0-9]+$/) >= 0) {
var ind = this.value.search(/[0-9]/);
msub = document.createElement('msub');
msub.appendChild(this.mi(this.value.substring(0,ind)));
msub.appendChild(this.mn(this.value.substring(ind)));
el = msub;
} else {
el = this.mi(this.value)
}
el.setAttribute("mathcolor", this.getColorForIdentifier(this.value, context));
if (this.value.indexOf('$') === 0)
el.setAttribute("fontfamily", "monospace");
return(el);
case ENode.NUMBER:
if (this.value.indexOf('e') != -1 || this.value.indexOf('E') != -1) {
var index = this.value.indexOf('e');
if (index == -1)
index = this.value.indexOf('E');
mrow = document.createElement('mrow');
mrow.appendChild(this.mn(this.value.substring(0, index)));
mrow.appendChild(this.mo("\u22C5"));
msup = document.createElement('msup');
msup.appendChild(this.mn(10));
msup.appendChild(this.mn(this.value.substring(index + 1)));
mrow.appendChild(msup);
return(mrow);
}
return(this.mn(this.value));
case ENode.OPERATOR:
if (this.value == "/") {
mfrac = document.createElement('mfrac');
mfrac.appendChild(c0._toMathML(context));
mfrac.appendChild(c1._toMathML(context));
el = mfrac;
} else if (this.value == "^") {
if (c0.type == ENode.FUNCTION) {
if (c0.value == "sqrt" || c0.value == "abs" || c0.value == "matrix" ||
c0.value == "diff")
par = false;
else
par = true;
} else if (c0.type == ENode.OPERATOR) {
par = true;
} else
par = false;
el = document.createElement('msup');
if (par)
el.appendChild(this.addP(c0, context));
else
el.appendChild(c0._toMathML(context));
el.appendChild(c1._toMathML(context));
} else if (this.value == "*") {
mrow = document.createElement('mrow');
if (c0.type == ENode.OPERATOR && (c0.value == "+" || c0.value == "-"))
mrow.appendChild(this.addP(c0, context));
else
mrow.appendChild(c0._toMathML(context));
// should the x operator be visible ? We need to check if there is a number to the left of c1
var firstinc1 = c1;
while (firstinc1.type == ENode.OPERATOR) {
firstinc1 = firstinc1.children[0];
}
// ... and if it's an operation between vectors/matrices, the * operator should be displayed
// (it is ambiguous otherwise)
// note: this will not work if the matrix is calculated, for instance with 2[1;2]*[3;4]
if (c0.type == ENode.VECTOR && c1.type == ENode.VECTOR)
mrow.appendChild(this.mo("*"));
else if (firstinc1.type == ENode.NUMBER)
mrow.appendChild(this.mo("\u22C5"));
else if (context.units)
mrow.appendChild(this.mo("\u22C5"));
else
mrow.appendChild(this.mo("\u2062")); // invisible multiplication
if (c1.type == ENode.OPERATOR && (c1.value == "+" || c1.value == "-"))
mrow.appendChild(this.addP(c1, context));
else
mrow.appendChild(c1._toMathML(context));
el = mrow;
} else if (this.value == "-") {
mrow = document.createElement('mrow');
if (this.children.length == 1) {
mrow.appendChild(this.mo("-"));
mrow.appendChild(c0._toMathML(context));
} else {
mrow.appendChild(c0._toMathML(context));
mrow.appendChild(this.mo("-"));
if (c1.type == ENode.OPERATOR && (c1.value == "+" || c1.value == "-"))
mrow.appendChild(this.addP(c1, context));
else
mrow.appendChild(c1._toMathML(context));
}
el = mrow;
} else if (this.value == "!") {
mrow = document.createElement('mrow');
mo = this.mo(this.value);
if (c0.type == ENode.OPERATOR && (c0.value == "+" || c0.value == "-"))
mrow.appendChild(this.addP(c0, context));
else
mrow.appendChild(c0._toMathML(context));
mrow.appendChild(mo);
el = mrow;
} else if (this.value == "+") {
mrow = document.createElement('mrow');
mo = this.mo(this.value);
mrow.appendChild(c0._toMathML(context));
mrow.appendChild(mo);
// should we add parenthesis ? We need to check if there is a '-' to the left of c1
par = false;
var first = c1;
while (first.type == ENode.OPERATOR) {
if (first.value == "-" && first.children.length == 1) {
par = true;
break;
} else if (first.value == "+" || first.value == "-" || first.value == "*") {
first = first.children[0];
} else {
break;
}
}
if (par)
mrow.appendChild(this.addP(c1, context));
else
mrow.appendChild(c1._toMathML(context));
el = mrow;
} else if (this.value == ".") {
mrow = document.createElement('mrow');
if (c0.type == ENode.OPERATOR && (c0.value == "+" || c0.value == "-"))
mrow.appendChild(this.addP(c0, context));
else
mrow.appendChild(c0._toMathML(context));
mrow.appendChild(this.mo("\u22C5"));
if (c1.type == ENode.OPERATOR && (c1.value == "+" || c1.value == "-"))
mrow.appendChild(this.addP(c1, context));
else
mrow.appendChild(c1._toMathML(context));
el = mrow;
} else if (this.value == "`") {
mrow = document.createElement('mrow');
if (c0.type == ENode.OPERATOR && (c0.value == "+" || c0.value == "-"))
mrow.appendChild(this.addP(c0, context));
else
mrow.appendChild(c0._toMathML(context));
// the units should not be in italics
// unit multiplication should not be displayed with an invisible operator
var mstyle = document.createElement("mstyle");
mstyle.setAttribute("fontstyle", "normal");
var units_context = {};
for (var prop in context) {
if (context.hasOwnProperty(prop)) {
units_context[prop] = context[prop];
}
}
units_context.units = true;
if (c1.type == ENode.OPERATOR && (c1.value == "+" || c1.value == "-"))
mstyle.appendChild(this.addP(c1, units_context));
else
mstyle.appendChild(c1._toMathML(units_context));
mrow.appendChild(mstyle);
el = mrow;
} else {
// relational operators
mrow = document.createElement('mrow');
mo = this.mo(this.value);
mrow.appendChild(c0._toMathML(context));
mrow.appendChild(mo);
mrow.appendChild(c1._toMathML(context));
el = mrow;
}
return(el);
case ENode.FUNCTION: /* TODO: throw exceptions if wrong nb of args ? */
// c0 contains the function name
if (c0.value == "sqrt" && c1 != null) {
el = document.createElement('msqrt');
el.appendChild(c1._toMathML(context));
} else if (c0.value == "abs" && c1 != null) {
mrow = document.createElement('mrow');
mrow.appendChild(this.mo("|"));
mrow.appendChild(c1._toMathML(context));
mrow.appendChild(this.mo("|"));
el = mrow;
} else if (c0.value == "exp" && c1 != null) {
el = document.createElement('msup');
el.appendChild(this.mi("e"));
el.appendChild(c1._toMathML(context));
} else if (c0.value == "factorial") {
mrow = document.createElement('mrow');
mo = this.mo("!");
if (c1.type == ENode.OPERATOR && (c1.value == "+" || c1.value == "-"))
mrow.appendChild(this.addP(c1, context));
else
mrow.appendChild(c1._toMathML(context));
mrow.appendChild(mo);
el = mrow;
} else if (c0.value == "diff" && this.children != null && this.children.length == 3) {
mrow = document.createElement('mrow');
mfrac = document.createElement('mfrac');
mfrac.appendChild(this.mi("d"));
var f2 = document.createElement('mrow');
f2.appendChild(this.mi("d"));
f2.appendChild(this.mi(c2.value));
mfrac.appendChild(f2);
mrow.appendChild(mfrac);
if (c1.type == ENode.OPERATOR && (c1.value == "+" || c1.value == "-"))
mrow.appendChild(this.addP(c1, context));
else
mrow.appendChild(c1._toMathML(context));
el = mrow;
} else if (c0.value == "diff" && this.children != null && this.children.length == 4) {
mrow = document.createElement('mrow');
mfrac = document.createElement('mfrac');
msup = document.createElement('msup');
msup.appendChild(this.mi("d"));
msup.appendChild(c3._toMathML(context));
mfrac.appendChild(msup);
var f2 = document.createElement('mrow');
f2.appendChild(this.mi("d"));
msup = document.createElement('msup');
msup.appendChild(c2._toMathML(context));
msup.appendChild(c3._toMathML(context));
f2.appendChild(msup);
mfrac.appendChild(f2);
mrow.appendChild(mfrac);
if (c1.type == ENode.OPERATOR && (c1.value == "+" || c1.value == "-"))
mrow.appendChild(this.addP(c1, context));
else
mrow.appendChild(c1._toMathML(context));
el = mrow;
} else if (c0.value == "integrate" && this.children != null && this.children.length == 3) {
mrow = document.createElement('mrow');
var mo = this.mo("\u222B");
mo.setAttribute("stretchy", "true"); // doesn't work with MathJax
mrow.appendChild(mo);
if (c2.type == ENode.OPERATOR && (c2.value == "+" || c2.value == "-"))
mrow.appendChild(this.addP(c1, context));
else
mrow.appendChild(c1._toMathML(context));
mrow.appendChild(this.mi("d"));
mrow.appendChild(c2._toMathML(context));
el = mrow;
} else if (c0.value == "integrate" && this.children != null && this.children.length == 5) {
mrow = document.createElement('mrow');
var msubsup = document.createElement('msubsup');
var mo = this.mo("\u222B");
mo.setAttribute("stretchy", "true"); // doesn't work with MathJax
msubsup.appendChild(mo);
msubsup.appendChild(c3._toMathML(context));
msubsup.appendChild(c4._toMathML(context));
mrow.appendChild(msubsup);
if (c2.type == ENode.OPERATOR && (c2.value == "+" || c2.value == "-"))
mrow.appendChild(this.addP(c1, context));
else
mrow.appendChild(c1._toMathML(context));
mrow.appendChild(this.mi("d"));
mrow.appendChild(c2._toMathML(context));
el = mrow;
} else if (c0.value == "sum" && this.children != null && this.children.length == 5) {
mrow = document.createElement('mrow');
var munderover = document.createElement('munderover');
var mo = this.mo("\u2211");
mo.setAttribute("stretchy", "true"); // doesn't work with MathJax
munderover.appendChild(mo);
mrow2 = document.createElement('mrow');
mrow2.appendChild(c2._toMathML(context));
mrow2.appendChild(this.mo("="));
mrow2.appendChild(c3._toMathML(context));
munderover.appendChild(mrow2);
munderover.appendChild(c4._toMathML(context));
mrow.appendChild(munderover);
if (c2.type == ENode.OPERATOR && (c2.value == "+" || c2.value == "-"))
mrow.appendChild(this.addP(c1, context));
else
mrow.appendChild(c1._toMathML(context));
el = mrow;
} else if (c0.value == "product" && this.children != null && this.children.length == 5) {
mrow = document.createElement('mrow');
var munderover = document.createElement('munderover');
var mo = this.mo("\u220F");
mo.setAttribute("stretchy", "true"); // doesn't work with MathJax
munderover.appendChild(mo);
mrow2 = document.createElement('mrow');
mrow2.appendChild(c2._toMathML(context));
mrow2.appendChild(this.mo("="));
mrow2.appendChild(c3._toMathML(context));
munderover.appendChild(mrow2);
munderover.appendChild(c4._toMathML(context));
mrow.appendChild(munderover);
if (c2.type == ENode.OPERATOR && (c2.value == "+" || c2.value == "-"))
mrow.appendChild(this.addP(c1, context));
else
mrow.appendChild(c1._toMathML(context));
el = mrow;
} else if (c0.value == "limit") {
mrow = document.createElement('mrow');
if (this.children.length < 4) {
mrow.appendChild(this.mo("lim"));
} else {
var munder = document.createElement('munder');
munder.appendChild(this.mo("lim"));
var mrowunder = document.createElement('mrow');
mrowunder.appendChild(c2._toMathML(context));
mrowunder.appendChild(this.mo("\u2192"));
mrowunder.appendChild(c3._toMathML(context));
if (c4 != null) {
if (c4.value == "plus")
mrowunder.appendChild(this.mo("+"));
else if (c4.value == "minus")
mrowunder.appendChild(this.mo("-"));
}
munder.appendChild(mrowunder);
mrow.appendChild(munder);
}
mrow.appendChild(c1._toMathML(context));
el = mrow;
} else if (c0.value == "binomial") {
// displayed like a vector
mrow = document.createElement('mrow');
mrow.appendChild(this.mo("("));
mtable = document.createElement('mtable');
for (i=1; i<this.children.length; i++) {
var mtr = document.createElement('mtr');
mtr.appendChild(this.children[i]._toMathML(context));
mtable.appendChild(mtr);
}
mrow.appendChild(mtable);
mrow.appendChild(this.mo(")"));
el = mrow;
} else if (c0.value == "matrix") {
for (i=1; i<this.children.length; i++) {
// check that all children are vectors
if (this.children[i].type !== ENode.VECTOR) {
el = document.createElement('mtext');
el.appendChild(document.createTextNode("???")); // could throw here
return(el);
}
}
mrow = document.createElement('mrow');
mrow.appendChild(this.mo("("));
mtable = document.createElement('mtable');
for (i=1; i<this.children.length; i++) {
var mtr = document.createElement('mtr');
for (j=0; j<this.children[i].children.length; j++) {
mtr.appendChild(this.children[i].children[j]._toMathML(context));
}
mtable.appendChild(mtr);
}
mrow.appendChild(mtable);
mrow.appendChild(this.mo(")"));
el = mrow;
} else if (c0.value == "union" && this.children.length == 3) {
for (i=1; i<this.children.length; i++) {
// check that all children are intervals or sets
if (this.children[i].type !== ENode.INTERVAL && this.children[i].type !== ENode.SET) {
el = document.createElement('mtext');
el.appendChild(document.createTextNode("???"));
return(el);
}
}
mrow = document.createElement('mrow');
mrow.appendChild(c1._toMathML(context));
mrow.appendChild(this.mo("\u222A"));
mrow.appendChild(c2._toMathML(context));
el = mrow;
} else if (c0.value == "intersection" && this.children.length == 3) {
for (i=1; i<this.children.length; i++) {
// check that all children are intervals or sets
if (this.children[i].type !== ENode.INTERVAL && this.children[i].type !== ENode.SET) {
el = document.createElement('mtext');
el.appendChild(document.createTextNode("???"));
return(el);
}
}
mrow = document.createElement('mrow');
mrow.appendChild(c1._toMathML(context));
mrow.appendChild(this.mo("\u2229"));
mrow.appendChild(c2._toMathML(context));
el = mrow;
} else {
// default display for a function
mrow = document.createElement('mrow');
mrow.appendChild(c0._toMathML(context));
mrow.appendChild(this.mo("("));
for (i=1; i<this.children.length; i++) {
mrow.appendChild(this.children[i]._toMathML(context));
if (i < this.children.length - 1)
mrow.appendChild(this.mo(Definitions.ARG_SEPARATOR));
}
mrow.appendChild(this.mo(")"));
el = mrow;
}
return(el);
case ENode.VECTOR:
var is_matrix = true;
for (i=0; i<this.children.length; i++) {
if (this.children[i].type !== ENode.VECTOR)
is_matrix = false;
}
mrow = document.createElement('mrow');
mrow.appendChild(this.mo("("));
mtable = document.createElement('mtable');
for (i=0; i<this.children.length; i++) {
var mtr = document.createElement('mtr');
if (is_matrix) {
for (j=0; j<this.children[i].children.length; j++) {
mtr.appendChild(this.children[i].children[j]._toMathML(context));
}
} else {
mtr.appendChild(this.children[i]._toMathML(context));
}
mtable.appendChild(mtr);
}
mrow.appendChild(mtable);
mrow.appendChild(this.mo(")"));
return(mrow);
case ENode.INTERVAL:
mrow = document.createElement('mrow');
if (this.interval_type == ENode.OPEN_OPEN || this.interval_type == ENode.OPEN_CLOSED) {
mo = this.mo("(");
} else {
mo = this.mo("[");
}
mrow.appendChild(mo);
mrow2 = document.createElement('mrow');
mrow2.appendChild(this.children[0]._toMathML(context));
mrow2.appendChild(this.mo(":"));
mrow2.appendChild(this.children[1]._toMathML(context));
mrow.appendChild(mrow2);
if (this.interval_type == ENode.OPEN_OPEN || this.interval_type == ENode.CLOSED_OPEN) {
mo = this.mo(")");
} else {
mo = this.mo("]");
}
mrow.appendChild(mo);
return(mrow);
case ENode.SET:
mrow = document.createElement('mrow');
mrow.appendChild(this.mo("{"));
mrow2 = document.createElement('mrow');
for (i=0; i<this.children.length; i++) {
if (i > 0)
mrow2.appendChild(this.mo(";"));
mrow2.appendChild(this.children[i]._toMathML(context));
}
mrow.appendChild(mrow2);
mrow.appendChild(this.mo("}"));
return(mrow);
case ENode.SUBSCRIPT:
msub = document.createElement('msub');
msub.appendChild(c0._toMathML(context));
if (this.children.length > 2) {
mrow = document.createElement('mrow');
for (i=1; i<this.children.length; i++) {
mrow.appendChild(this.children[i]._toMathML(context));
if (i < this.children.length - 1)
mrow.appendChild(this.mo(Definitions.ARG_SEPARATOR));
}
msub.appendChild(mrow);
} else {
msub.appendChild(c1._toMathML(context));
}
return(msub);
}
};
/**
* Creates a MathML mi element with the given name
* @param {string} name
* @returns {Element}
*/
ENode.prototype.mi = function(name) {
var mi = document.createElement('mi');
if (ENode.symbols[name])
name = ENode.symbols[name];
mi.appendChild(document.createTextNode(name));
return mi;
};
/**
* Creates a MathML mn element with the given number or string
* @param {string} n
* @returns {Element}
*/
ENode.prototype.mn = function(n) {
var mn = document.createElement('mn');
mn.appendChild(document.createTextNode(n));
return mn;
};
/**
* Creates a MathML mo element with the given name
* @param {string} name
* @returns {Element}
*/
ENode.prototype.mo = function(name) {
var mo = document.createElement('mo');
if (ENode.symbols[name])
name = ENode.symbols[name];
mo.appendChild(document.createTextNode(name));
return mo;
};
/**
* Add parenthesis and returns a MathML element
* @param {ENode} en
* @param {Object} [context] - display context (not needed for the root element)
* @param {Object.<string, string>} context.hcolors - hash identifier->color
* @param {number} context.depth - Depth in parenthesis, used for coloring
* @returns {Element}
*/
ENode.prototype.addP = function(en, context) {
var mrow, mo;
mrow = document.createElement('mrow');
mo = this.mo("(");
var colors;
if (context.colors)
colors = context.colors;
else
colors = ENode.COLORS;
mo.setAttribute("mathcolor", colors[context.depth % colors.length]);
mrow.appendChild(mo);
context.depth++;
mrow.appendChild(en._toMathML(context));
context.depth--;
mo = this.mo(")");
mo.setAttribute("mathcolor", colors[context.depth % colors.length]);
mrow.appendChild(mo);
return mrow;
};
ENode.symbols = {
/* lowercase greek */
"alpha": "\u03B1", "beta": "\u03B2", "gamma": "\u03B3",
"delta": "\u03B4", "epsilon": "\u03B5", "zeta": "\u03B6",
"eta": "\u03B7", "theta": "\u03B8", "iota": "\u03B9",
"kappa": "\u03BA", "lambda": "\u03BB", "mu": "\u03BC",
"nu": "\u03BD", "xi": "\u03BE", "omicron": "\u03BF",
"pi": "\u03C0", "rho": "\u03C1", "sigma": "\u03C3",
"tau": "\u03C4", "upsilon": "\u03C5", "phi": "\u03C6",
"chi": "\u03C7", "psi": "\u03C8", "omega": "\u03C9",
/* uppercase greek */
"Alpha": "\u0391", "Beta": "\u0392", "Gamma": "\u0393",
"Delta": "\u0394", "Epsilon": "\u0395", "Zeta": "\u0396",
"Eta": "\u0397", "Theta": "\u0398", "Iota": "\u0399",
"Kappa": "\u039A", "Lambda": "\u039B", "Mu": "\u039C",
"Nu": "\u039D", "Xi": "\u039E", "Omicron": "\u039F",
"Pi": "\u03A0", "Rho": "\u03A1", "Sigma": "\u03A3",
"Tau": "\u03A4", "Upsilon": "\u03A5", "Phi": "\u03A6",
"Chi": "\u03A7", "Psi": "\u03A8", "Omega": "\u03A9",
/* operators */
"#": "\u2260",
">=": "\u2265",
"<=": "\u2264",
/* other */
"inf": "\u221E",
"minf": "-\u221E",
"hbar": "\u210F",
"G": "\uD835\uDCA2" // 1D4A2
};
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>