MathML generation algorithm for Javascript. Any recommended references - javascript

I am trying to build code that can generate MathML for Traditional Math Input. I am using JavaScript for coding. Are there any references or recommended readings I can go through to get a grasp of required algorithm? I am reading W3C standards for MathML which is a reference for standards but not for algorithm.
For Instance, for a sample input of
sqrt 9 * 5 + 20
I want to generate the MathML expression like below
<math xmlns='w3.org/1998/Math/MathML'>; <mrow> <mrow> <mn>5</mn> <mo>⁢</mo> <mn>9</mn> <mo>⁢</mo> <mi>SQRT</mi> </mrow> <mo>+</mo> <mn>20</mn> </mrow> </math>

I found a nice tutorial on MathML here: http://rypress.com/tutorials/mathml/basic-algebra.html and started to work out a very basic algebraic parser (for example, 4*sqrt(x+6)=(5-z)*y/7) with a crude stack for handling parentheses and an example sqrt function. Is this the direction you are after?
jsfiddle here: http://jsfiddle.net/alhambra1/bSJyE/
JavaScript code:
<script>
document.write('<p><input id="input" size=50>')
document.write('<button onclick="convertToMathML()">Convert</button></p>')
document.write('<div id="output"></div>')
function lex(str,ptr){
var ascii = str.charCodeAt(ptr),
lexeme = {string: "", type: ""},
operators = {"+": "+"
, "-": "-"
, "*": "×"
, "/": "÷"
, "=": "="},
functions = {sqrt: "msqrt"}
//identify type
if (ascii == 41)
lexeme.type = "closeBracket"
else if (ascii == 40){
lexeme.type = "func"
lexeme.func = "mfenced"
}
else if (ascii > 45 && ascii < 58 && ascii != 47)
lexeme.type = "mn"
else if ((ascii > 64 && ascii < 91) || (ascii > 96 && ascii < 123)){
for (i in functions){
if (str.substr(ptr,i.length).toLowerCase() == i){
lexeme.type = "func"
lexeme.func = functions[i]
ptr += i.length - 1
} else
lexeme.type = "mi"
}
} else if (!operators[str.charAt(ptr)])
return {string: str.charAt(ptr), type: "error", pointer: ptr}
else
lexeme.type = "mo"
switch (lexeme.type){
case "mo":
lexeme.string = operators[str.charAt(ptr++)]
break
default:
lexeme.string = str.charAt(ptr++)
break
}
ascii = str.charCodeAt(ptr)
//identify numbers and functions
if (lexeme.type == "mn"){
while (ptr < str.length && ascii > 45 && ascii < 58 && ascii != 47){
lexeme.string += str.charAt(ptr)
ascii = str.charCodeAt(++ptr)
}
} else if (lexeme.type == "func" && lexeme.func != "mfenced"){
while (ptr < str.length && str.substr(ptr).match(/^\s/)){
ascii = str.charCodeAt(++ptr)
}
if (str.charAt(ptr) != "(")
return {string: str.charAt(ptr), type: "error", pointer: ptr}
else
ptr++
}
lexeme["pointer"] = ptr
return lexeme
}
function markup(lexeme){
return "<" + lexeme.type + ">\n"
+ lexeme.string + "\n"
+ "</" + lexeme.type + ">\n"
}
function convertToMathML(){
var str = document.getElementById('input').value,
expression = "",
ptr = 0,
stack = []
while (ptr < str.length){
var currLexeme = lex(str,ptr)
if (currLexeme.type == "closeBracket"){
if (stack.length == 0)
expression = "Extra bracket at: " + (currLexeme.pointer - 1)
else
expression += "</" + stack.pop().func + ">\n"
+ "</mrow>"
ptr = currLexeme.pointer
} else if (currLexeme.type == "error"){
expression = "Cannot parse \"" + currLexeme.string
+ "\" at " + currLexeme.pointer
break
} else if (currLexeme.type == "func"){
expression += "<" + currLexeme.func + ">\n"
+ "<mrow>\n"
stack.push(currLexeme)
ptr = currLexeme.pointer
} else {
expression += markup (currLexeme)
ptr = currLexeme.pointer
}
}
if (ptr >= str.length && stack.length > 0)
expression = "Missing " + stack.length + " closing bracket/s."
expression = "<math xmlns='http://www.w3.org/1998/Math/MathML'>"
+ expression + "</math>"
document.getElementById('output').innerHTML = expression
}
</script>

Related

How to replace a single index of a string for a number?

I want to replace vowels in a string with numbers, but I can not get it working.
This code I am showing, I am only doing the vowel "e", here is my code:
var string = prompt("Enter anything with letters only");
console.log("You wrote: " + string);
for(k=0; k<string.length; k++){
if(string[k]=="a"){
console.log(string[k]);
}
if(string[k]=="e" || string[k]=="E"){
console.log("found: " + string[k]);
string[k] = "2" ; // Here is where I am trying to replace the vowels "e" to value 2
}
if(string[k]=="i" || string[k]=="I"){
console.log("found: " + string[k]);
}
if(string[k]=="o" || string[k]=="O"){
console.log("found: " + string[k]);
}
if(string[k]=="u" || string[k]=="U"){
console.log("found: " + string[k]);
}
}
console.log(string);
Please note I am very new to javascript, this is why I am doing simple for loop exercises with no special comands or shorcuts.
I have tried: string[k] = 2 and also string[k] = (variable with the value 2)
Any sugestions?
Strings are immutable. You could use an array and change the items/characters. Later you could join the array back to a string.
var string = prompt("Enter anything with letters only"),
array = string.split('');
console.log("You wrote: " + string);
for (k = 0; k < array.length; k++) {
if (array[k] == "a") {
console.log(string[k]);
}
if (array[k] == "e" || array[k] == "E") {
console.log("found: " + array[k]);
array[k] = "2";
}
if (array[k] == "i" || array[k] == "I") {
console.log("found: " + array[k]);
}
if (array[k] == "o" || array[k] == "O") {
console.log("found: " + array[k]);
}
if (array[k] == "u" || array[k] == "U") {
console.log("found: " + array[k]);
}
}
string = array.join('');
console.log(string);
A different approach by using a regular expression and an object for replacing the characters with String#replace.
var string = prompt("Enter anything with letters only");
string = string.replace(/[aeiou]/gi, c => ({ a: 1, e: 2, i: 3, o: 4, u: 5}[c.toLowerCase()]));
console.log(string);
You could use Regular Expressions
var string=prompt("Enter anything with letters only");
console.log("You wrote: " + string);
string = string.replace(/a/g, 1);
string = string.replace(/e/g, 2);
string = string.replace(/i/g, 3);
string = string.replace(/o/g, 4);
string = string.replace(/u/g, 5);
console.log(string);
this may not be the best way to do it, it's not looking very clean. The modifier g stands for global-search, it will find all occurences.
you can use regular expressions for that
var string = prompt("Enter anything with letters only");
console.log("You wrote: " + string);
string = string.replace(/a/gi, 1);
string = string.replace(/e/gi, 2);
string = string.replace(/i/gi, 3);
string = string.replace(/o/gi, 4);
string = string.replace(/u/gi, 5);
console.log(string);
gi makes the search global and case insensitive but if you insist on using a loop you can do this
var string = prompt("Enter anything with letters only");
console.log("You wrote: " + string);
var string2 = "";
for (var k = 0; k < string.length; k++) {
if (string[k] == "a") {
console.log(string[k]);
string2 += "1";
} else if (string[k] == "e" || string[k] == "E") {
console.log("found: " + string[k]);
string2 += "2";
} else if (string[k] == "i" || string[k] == "I") {
console.log("found: " + string[k]);
string2 += "3";
} else if (string[k] == "o" || string[k] == "O") {
console.log("found: " + string[k]);
string2 += "4";
} else if (string[k] == "u" || string[k] == "U") {
console.log("found: " + string[k]);
string2 += "5";
} else {
string2 += string[k];
}
}
console.log(string2);
//Prompt: type in word, example: "lolvowelslol" & display this in console
var str = prompt("Enter anything with letters only");
console.log("You wrote: " + string);
//Type in what wordt you want to find
var word = "vowels";
//Regex: Find the index range of the word, than replace it with the second string
var result = str.replace(word, "123456")
Bonus:
var str = "vvowels";
var word = "vowels";
var toNumbers = function(word) {
var numbers = "";
for(var i=0;i<word.length;i++) numbers += i+1;
return numbers;
};
var result = str.replace(word, toNumbers(word));

unterminated string constant with if else

I am coding a quiz and need to have it display a certain answer depending on the score.
I had this working when I just had the score being displayed, but now that I have added in the if else, it has stopped working and I am getting an unterminated string constant
$total_score = $first_question + $second_question + $third_question + $fourth_question + $fifth_question + $sixth_question + $seventh_question + $eighth_question + $ninth_question + $tenth_question + $eleventh_question + $twelfth_question + $thirteenth_question + $fourteenth_question + $fifthteenth_question + $sixteenth_question ;
});
$("#btn").click(function() {
$('#reveal').html("<h3><strong>Total score</strong> " + $total_score +"</h3>");
if ($total_score >= 13) {
$('#answer').html("<h3><strong>13-16: Answer</strong></h3>");
} else if ($total_score >=9 && $total_score <= 12) {
$('#answer').html("<h3><strong>9-12: answer</strong></h3>");
} else if ($total_score >=5 && $total_score <= 8) {
$('#answer').html("<h3><strong>5-8: answer</strong></h3>");
} else {
$('#answer').html("<h3><strong> everything else</strong></h3>");
}
});
In addition to the line breaks as #Spork noted, you need to remove the extraneous "})" after the $total_score calculation:
$total_score = $first_question + $second_question + $third_question + $fourth_question + $fifth_question + $sixth_question + $seventh_question + $eighth_question + $ninth_question + $tenth_question + $eleventh_question + $twelfth_question + $thirteenth_question + $fourteenth_question + $fifthteenth_question + $sixteenth_question ;
/* must remove the following:
});
*/
$("#btn").click(function() {
$('#reveal').html("<h3><strong>Total score</strong> " + $total_score +"</h3>");
if ($total_score >= 13) {
$('#answer').html("<h3><strong>13-16: Answer </strong></h3>");
} else if ($total_score >=9 && $total_score <= 12) {
$('#answer').html("<h3><strong>9-12: answer </strong></h3>");
} else if ($total_score >=5 && $total_score <= 8) {
$('#answer').html("<h3><strong>5-8: answer </strong></h3>");
} else {
$('#answer').html("<h3><strong> everything else</strong></h3>");
}
});
Javascript treats line ends roughly as semicolons (;), so you cannot have multi-line strings as you have in your script. Remove them, and it'll be okay.

Javascript function - works in IE, not in chrome

To preface this, we are a small organization and this system was built by someone long ago. I am a total novice at javascript so I have trouble doing complicated things, but I will do my best to understand your answers. But unfortunately redoing everything from scratch is not really an option at this point.
We have a system of collecting data where clients use a login to verify a member ID, which the system then uses to pull records from an MS Access database to .ASP/html forms so clients can update their data. One of these pages has the following function that runs on form submit to check that data in fields a/b/c sum to the same total as d/e/f/g/h/i. It does this separately for each column displayed (each column is a record in the database, each a/b/c/d/e/f is a field in the record.)
The problem is with this section of the function:
for (var j=0; j<recCnt; j++) {
sumByType = milesSurf[j] + milesElev[j] + milesUnder[j];
sumByTrack = milesSingle[j] + milesDouble[j] + milesTriple[j] + milesQuad[j] + milesPent[j] + milesSex[j];
etc.
It should use javascript FOR to loop through each record and test to see if they sum to the same thing.
In Firefox and IE this is working properly; the fields sum properly into "sumByType" and "sumByTrack". You can see below I added a little alert to figure out what was going wrong:
alert(sumByType + " " + j + " " + recCnt + " " + milesSurf[j] + " " + milesElev[j] + " " + milesUnder[j]);
In Chrome, that alert tells me that the components of "sumByType" and "sumByTrack" (the various "milesXXXXX" variables) are undefined.
My question is: Why in Chrome is this not working properly, when in IE and FFox it is? Any ideas?
Full function code below:
function submitCheck(formy, recCnt) {
//2/10/03: added milesQuad
//---------------checks Q#4 that Line Mileage by type is the same as by track
var milesElev = new Array();
var milesSurf = new Array();
var milesUnder = new Array();
var milesSingle = new Array();
var milesDouble = new Array();
var milesTriple = new Array();
var milesQuad = new Array();
var milesPent = new Array();
var milesSex = new Array();
var sumByType = 0;
var milesLineTrack = new Array(); //this is for Q5 to compare it to mileage by trackage
var j = 0; var sumByTrack = 0; var liney; var yrOp;
//var str = "document.frm.milesElev" + j;
//alert(str.value);
for (var i in document.frm) {
if (i.substring(0, i.length - 1) == "milesElev") {
milesElev[parseInt(i.substring(i.length-1, i.length))] = parseFloat(document.frm[i].value); }
if (i.substring(0, i.length - 1) == "milesSurf") {
milesSurf[parseInt(i.substring(i.length-1, i.length))] = parseFloat(document.frm[i].value); }
if (i.substring(0, i.length - 1) == "milesUnder") {
milesUnder[parseInt(i.substring(i.length-1, i.length))] = parseFloat(document.frm[i].value); }
if (i.substring(0, i.length - 1) == "milesSingle") {
milesSingle[parseInt(i.substring(i.length-1, i.length))] = parseFloat(document.frm[i].value); }
if (i.substring(0, i.length - 1) == "milesDouble") {
milesDouble[parseInt(i.substring(i.length-1, i.length))] = parseFloat(document.frm[i].value); }
if (i.substring(0, i.length - 1) == "milesTriple") {
milesTriple[parseInt(i.substring(i.length-1, i.length))] = parseFloat(document.frm[i].value); }
if (i.substring(0, i.length - 1) == "milesQuad") {
milesQuad[parseInt(i.substring(i.length-1, i.length))] = parseFloat(document.frm[i].value); }
if (i.substring(0, i.length - 1) == "milesPent") {
milesPent[parseInt(i.substring(i.length-1, i.length))] = parseFloat(document.frm[i].value); }
if (i.substring(0, i.length - 1) == "milesSex") {
milesSex[parseInt(i.substring(i.length-1, i.length))] = parseFloat(document.frm[i].value); }
if (i.substring(0, i.length -1) == "milesLineTrack") {
milesLineTrack[parseInt(i.substring(i.length-1, i.length))] = document.frm[i].value; } //12/13/02 used to be parseFloat(document.frm[i].value)
if (i.substring(0,5)=="Lines") {
liney = document.frm[i].value;
if (parseInt(liney)<1 || isNaN(liney)) {
alert("Each mode must have at least 1 line. Please correct the value in question #2.");
document.frm[i].select(); return false; }}
if (i.substring(0,8)=="yearOpen") {
yrOp = document.frm[i].value;
if (parseInt(yrOp)<1825 || isNaN(yrOp)) {
alert("Please enter a year after 1825 for question #3");
document.frm[i].select(); return false; }
}
}
for (var j=0; j<recCnt; j++) {
sumByType = milesSurf[j] + milesElev[j] + milesUnder[j];
sumByTrack = milesSingle[j] + milesDouble[j] + milesTriple[j] + milesQuad[j] + milesPent[j] + milesSex[j];
//---------------to round sumByTrack and sumByType from a long decimal to a single decimal place, like frm 7.89999998 to 7.9.
sumByTrack = sumByTrack * 10;
if (sumByTrack != parseInt(sumByTrack)) {
if (sumByTrack - parseInt(sumByTrack) >= .5) {
//round up
sumByTrack = parseInt(sumByTrack) + 1; }
else { //truncate
sumByTrack = parseInt(sumByTrack); }}
sumByTrack = sumByTrack / 10;
sumByType = sumByType * 10;
if (sumByType != parseInt(sumByType)) {
if (sumByType - parseInt(sumByType) >= .5) {
//round up
sumByType = parseInt(sumByType) + 1; }
else { //truncate
sumByType = parseInt(sumByType); }}
sumByType = sumByType / 10;
//-------------end of rounding ---------------------------
if (sumByType != sumByTrack) {
if (isNaN(sumByType)) {
sumByType = "(sum of 4.a., b., and c.) "; }
else {
sumByType = "of " + sumByType; }
if (isNaN(sumByTrack)) {
sumByTrack = "(sum of 4.d., e., f., g., h., and i.) "; }
else {
sumByTrack = "of " + sumByTrack; }
alert("For #4, the 'End-to-End Mileage By Type' " + sumByType + " must equal the 'End-to-end Mileage By Trackage' " + sumByTrack + ".");
alert(sumByType + " " + j + " " + recCnt + " " + milesSurf[j] + " " + milesElev[j] + " " + milesUnder[j]);
return false;
}
//alert (milesLineTrack[j] + " " + milesSingle[j] + " " + 2*milesDouble[j] + " " + 3*milesTriple[j] + " " + 4*milesQuad[j] + " " + 5*milesPent[j] + " " + 6*milesSex[j]);
var singDoubTrip = (milesSingle[j] + 2*milesDouble[j] + 3*milesTriple[j] + 4*milesQuad[j] + 5*milesPent[j] + 6*milesSex[j])
//----------round singDoubTrip to one digit after the decimal point (like from 6.000000001 to 6.0)
singDoubTrip = singDoubTrip * 10;
if (singDoubTrip != parseInt(singDoubTrip)) {
if (singDoubTrip - parseInt(singDoubTrip) >= .5) {
//round up
singDoubTrip = parseInt(singDoubTrip) + 1; }
else { //truncate
singDoubTrip = parseInt(singDoubTrip); }}
singDoubTrip = singDoubTrip / 10;
//----------end round singDoubTrip-----------------------------------------
if (parseFloat(milesLineTrack[j]) != singDoubTrip) {
//var mlt = milesLineTrack[j];
//if isNaN(milesLineTrack[j]) { mlt =
alert("For column #" + (j+1) + ", the mainline passenger track mileage of " + milesLineTrack[j] + " must equal the single track plus 2 times the double track plus 3 times the triple track plus 4 times the quadruple track plus 5 times the quintuple track plus 6 times the sextuple track, which is " + singDoubTrip + ".");
return false;
}
}
//---------------------end of checking Q#4----------------
//return false;
}
I think for (var i in document.frm) is the problem. You should not enumerate a form element, there will be plenty of unexpected properties - see Why is using "for...in" with array iteration a bad idea?, which is especially true for array-like objects. I can't believe this works properly in FF :-)
Use this:
var ele = document.frm.elements; // or even better document.getElementById("frm")
for (var i=0; i<ele.length; i++) {
// use ele[i] to access the element,
// and ele[i].name instead of i where you need the name
}
Also, you should favour a loop over those gazillion of if-statements.

Interesting behavior in my US Number formatting code

I'm trying to make 10 digits look like a US telephone number (i.e.(###) ###-####). My code does accomplish this first goal, but it also does something I can't quite figure out. When typing in the digits, the characters "()" show up before typing any other digits. I want the open parenthesis to appear first and the closing parathesis to appear after entering the third digit. Please don't give me a new solution; try to pin point the issue I'm describing.
<script type="text/javascript">
$('.drumbi-caller-number').live('keydown', function (event) {
if (event.keyCode == 8 || event.keyCode == 37 || event.keyCode == 39) {
} else {
inputval = $(this).val();
var string = inputval.replace(/[^0-9]/g, "");
var first3 = string.substring(0,3);
var next3 = string.substring(3,6);
var next4 = string.substring(6,9);
var string = ("(" + first3 + ")" + next3 + "-" + next4);
$(this).val(string);
}
});
</script>
Here's a jsFiddle that displays this behavior: http://jsfiddle.net/bigthyme/j6kHn/3/
replace keydown with keyup, on keydown the value of the input element isn't updated
also set your string conditionally, only if long enough:
var string = string.length > 2 ? ("(" + first3 + ")" + next3 + "-" + next4) : first3;
here is the code: http://jsfiddle.net/j6kHn/10
btw: you should also replace .live(...) with .on(...) as .live() is deprecated..
You need to check the length of first3 before appending the paren:
var string = ("(" + first3 + ((first3.length>=3)?")":"") + next3 + "-" + next4);
And although not in your question, you can do the same for the hyphen:
var string = ("(" + first3 +
// only append the ) if the you have 3+ chars
((first3.length>=3)?")":"") +
next3 +
// only append the - if the you have 6+ chars
(((first3+next3).length>=6)?"-":"") +
next4);
You should also use .on() instead of live();
See it all working in this jsFiddle
Go with
$('.foo').on('keyup', function (event) {
$(this).val($(this).val().replace(/\D/g, "").replace(/(\d{0,3})(\d{0,3})(\d{0,4}).*/, "($1) $2-$3"));
});
Test this code here.
Try using this code, it should fix all of your issues:
Demo: http://jsfiddle.net/bN6Rh/3/
jQuery:
$('.foo').on('keyup', function(event) {
if (event.keyCode == (8 || 37 || 39)) { }
else {
inputval = $(this).val();
var string = inputval.replace(/[^0-9]/g, "");
var first3 = string.substring(0, 3);
var next3 = " " + string.substring(3, 6);
var next4 = string.substring(6, 10);
if (string.length < 3) { // Less than 3
var string = "(" + first3;
}
else if (string.length > 2 && string.length < 7) { // More than 2 and less than 7
var string = "(" + first3 + ")" + next3;
}
else { // Anything else
var string = "(" + first3 + ")" + next3 + "-" + next4;
}
$(this).val(string);
}
});​
The problem was that you weren't checking the number of characters so as soon as anything was entered it put in ()-, the above code also adds the space you mentioned wanting.
The code could of course be more compressed:
$('.foo').on('keyup', function(e) {
if (e.keyCode == (8 || 37 || 39));
else {
var str = this.value.replace(/[^0-9]/g, "");
var f3 = str.substring(0, 3),
n3 = " " + str.substring(3, 6),
n4 = str.substring(6, 10);
if (str.length<3) str = "(" + f3;
else if (str.length>2&&str.length<7) str="("+f3+")"+n3;
else str="("+f3+")"+n3+"-"+n4;
this.value = str;
}
});​

Capitalize and color the first letter after a period in jquery

I'm trying figure out how to color the first word after the period.
I worked out capitalizing the first letter, but now I need it be colored. I preferred to be the first letter "red".
$(function () {
function capitalizeSentences(capText, capLock) {
if (capLock == 1 || capLock == true) {
capText = capText.toLowerCase();
}
capText = capText.replace(/\.\n/g, ".[-<br>-]. ");
capText = capText.replace(/\.\s\n/g, ". [-<br>-]. ");
var wordSplit = '. ';
var wordArray = capText.split(wordSplit);
var numWords = wordArray.length;
for (x = 0; x < numWords; x++) {
wordArray[x] = wordArray[x].replace(wordArray[x].charAt(0), wordArray[x].charAt(0).toUpperCase());
if (x == 0) {
capText = wordArray[x] + ". ";
} else if (x != numWords - 1) {
capText = capText + wordArray[x] + ". ";
} else if (x == numWords - 1) {
capText = capText + wordArray[x];
}
}
capText = capText.replace(/\[-<br>-\]\.\s/g, "\n");
capText = capText.replace(/\si\s/g, " I ");
return capText;
}
//Capitalize After Period
$("#capitalizeAfterPeriod").click(function () {
var txt = $('textarea#generator').val();
txt = capitalizeSentences(txt, true);
$('textarea#generator').val(txt).addClass('period');
});
});
You can't add color to one letter in a textarea because textareas don't support rich text, if you want to use rich text you either need to use an element with the contenteditable attribute and then change the line to this:
wordArray[x] = wordArray[x].replace(wordArray[x].charAt(0), '<span style="color:#color;">'+wordArray[x].charAt(0).toUpperCase()+'</span>');

Categories