mhchem.js (15398B)
1 /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ 2 /* vim: set ts=2 et sw=2 tw=80: */ 3 4 /************************************************************* 5 * 6 * MathJax/extensions/TeX/mhchem.js 7 * 8 * Implements the \ce command for handling chemical formulas 9 * from the mhchem LaTeX package. 10 * 11 * --------------------------------------------------------------------- 12 * 13 * Copyright (c) 2011-2015 The MathJax Consortium 14 * 15 * Licensed under the Apache License, Version 2.0 (the "License"); 16 * you may not use this file except in compliance with the License. 17 * You may obtain a copy of the License at 18 * 19 * http://www.apache.org/licenses/LICENSE-2.0 20 * 21 * Unless required by applicable law or agreed to in writing, software 22 * distributed under the License is distributed on an "AS IS" BASIS, 23 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 * See the License for the specific language governing permissions and 25 * limitations under the License. 26 */ 27 28 MathJax.Extension["TeX/mhchem"] = { 29 version: "2.6.0" 30 }; 31 32 MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () { 33 34 var TEX = MathJax.InputJax.TeX; 35 36 /* 37 * This is the main class for handing the \ce and related commands. 38 * Its main method is Parse() which takes the argument to \ce and 39 * returns the corresponding TeX string. 40 */ 41 42 var CE = MathJax.Object.Subclass({ 43 string: "", // the \ce string being parsed 44 i: 0, // the current position in the string 45 tex: "", // the partially processed TeX result 46 TEX: "", // the full TeX result 47 atom: false, // last processed token is an atom 48 sup: "", // pending superscript 49 sub: "", // pending subscript 50 presup: "", // pending pre-superscript 51 presub: "", // pending pre-subscript 52 53 // 54 // Store the string when a CE object is created 55 // 56 Init: function (string) {this.string = string}, 57 58 // 59 // These are the special characters and the methods that 60 // handle them. All others are passed through verbatim. 61 // 62 ParseTable: { 63 '-': "Minus", 64 '+': "Plus", 65 '(': "Open", 66 ')': "Close", 67 '[': "Open", 68 ']': "Close", 69 '<': "Less", 70 '^': "Superscript", 71 '_': "Subscript", 72 '*': "Dot", 73 '.': "Dot", 74 '=': "Equal", 75 '#': "Pound", 76 '$': "Math", 77 '\\': "Macro", 78 ' ': "Space" 79 }, 80 // 81 // Basic arrow names for reactions 82 // 83 Arrows: { 84 '->': "rightarrow", 85 '<-': "leftarrow", 86 '<->': "leftrightarrow", 87 '<=>': "rightleftharpoons", 88 '<=>>': "Rightleftharpoons", 89 '<<=>': "Leftrightharpoons", 90 '^': "uparrow", 91 'v': "downarrow" 92 }, 93 94 // 95 // Implementations for the various bonds 96 // (the ~ ones are hacks that don't work well in NativeMML) 97 // 98 Bonds: { 99 '-': "-", 100 '=': "=", 101 '#': "\\equiv", 102 '~': "\\tripledash", 103 '~-': "\\begin{CEstack}{}\\tripledash\\\\-\\end{CEstack}", 104 '~=': "\\raise2mu{\\begin{CEstack}{}\\tripledash\\\\-\\\\-\\end{CEstack}}", 105 '~--': "\\raise2mu{\\begin{CEstack}{}\\tripledash\\\\-\\\\-\\end{CEstack}}", 106 '-~-': "\\raise2mu{\\begin{CEstack}{}-\\\\\\tripledash\\\\-\\end{CEstack}}", 107 '...': "{\\cdot}{\\cdot}{\\cdot}", 108 '....': "{\\cdot}{\\cdot}{\\cdot}{\\cdot}", 109 '->': "\\rightarrow", 110 '<-': "\\leftarrow", 111 '??': "\\text{??}" // unknown bond 112 }, 113 114 // 115 // This converts the CE string to a TeX string. 116 // It loops through the string and calls the proper 117 // method depending on the ccurrent character. 118 // 119 Parse: function () { 120 this.tex = ""; this.atom = false; 121 while (this.i < this.string.length) { 122 var c = this.string.charAt(this.i); 123 if (c.match(/[a-z]/i)) {this.ParseLetter()} 124 else if (c.match(/[0-9]/)) {this.ParseNumber()} 125 else {this["Parse"+(this.ParseTable[c]||"Other")](c)} 126 } 127 this.FinishAtom(true); 128 return this.TEX; 129 }, 130 131 // 132 // Make an atom name or a down arrow 133 // 134 ParseLetter: function () { 135 this.FinishAtom(); 136 if (this.Match(/^v( |$)/)) { 137 this.tex += "{\\"+this.Arrows["v"]+"}"; 138 } else { 139 this.tex += "\\text{"+this.Match(/^[a-z]+/i)+"}"; 140 this.atom = true; 141 } 142 }, 143 144 // 145 // Make a number or fraction preceeding an atom, 146 // or a subscript for an atom. 147 // 148 ParseNumber: function () { 149 var n = this.Match(/^\d+/); 150 if (this.atom && !this.sub) { 151 this.sub = n; 152 } else { 153 this.FinishAtom(); 154 var match = this.Match(/^\/\d+/); 155 if (match) { 156 var frac = "\\frac{"+n+"}{"+match.substr(1)+"}"; 157 this.tex += "\\mathchoice{\\textstyle"+frac+"}{"+frac+"}{"+frac+"}{"+frac+"}"; 158 } else { 159 this.tex += n; 160 if (this.i < this.string.length) {this.tex += "\\,"} 161 } 162 } 163 }, 164 165 // 166 // Make a superscript minus, or an arrow, or a single bond. 167 // 168 ParseMinus: function (c) { 169 if (this.atom && (this.i === this.string.length-1 || this.string.charAt(this.i+1) === " ")) { 170 this.sup += c; 171 } else { 172 this.FinishAtom(); 173 if (this.string.substr(this.i,2) === "->") {this.i += 2; this.AddArrow("->"); return} 174 else {this.tex += "{-}"} 175 } 176 this.i++; 177 }, 178 179 // 180 // Make a superscript plus, or pass it through 181 // 182 ParsePlus: function (c) { 183 if (this.atom) {this.sup += c} else {this.FinishAtom(); this.tex += c} 184 this.i++; 185 }, 186 187 // 188 // Handle dots and double or triple bonds 189 // 190 ParseDot: function (c) {this.FinishAtom(); this.tex += "\\cdot "; this.i++}, 191 ParseEqual: function (c) {this.FinishAtom(); this.tex += "{=}"; this.i++}, 192 ParsePound: function (c) {this.FinishAtom(); this.tex += "{\\equiv}"; this.i++}, 193 194 // 195 // Look for (v) or (^), or pass it through 196 // 197 ParseOpen: function (c) { 198 this.FinishAtom(); 199 var match = this.Match(/^\([v^]\)/); 200 if (match) {this.tex += "{\\"+this.Arrows[match.charAt(1)]+"}"} 201 else {this.tex += "{"+c; this.i++} 202 }, 203 // 204 // Allow ) and ] to get super- and subscripts 205 // 206 ParseClose: function (c) {this.FinishAtom(); this.atom = true; this.tex += c+"}"; this.i++}, 207 208 // 209 // Make the proper arrow 210 // 211 ParseLess: function (c) { 212 this.FinishAtom(); 213 var arrow = this.Match(/^(<->?|<=>>?|<<=>)/); 214 if (!arrow) {this.tex += c; this.i++} else {this.AddArrow(arrow)} 215 }, 216 217 // 218 // Look for a superscript, or an up arrow 219 // 220 ParseSuperscript: function (c) { 221 c = this.string.charAt(++this.i); 222 if (c === "{") { 223 this.i++; var m = this.Find("}"); 224 if (m === "-.") {this.sup += "{-}{\\cdot}"} 225 else if (m) {this.sup += CE(m).Parse().replace(/^\{-\}/,"-")} 226 } else if (c === " " || c === "") { 227 this.tex += "{\\"+this.Arrows["^"]+"}"; this.i++; 228 } else { 229 var n = this.Match(/^(\d+|-\.)/); 230 if (n) {this.sup += n} 231 } 232 }, 233 // 234 // Look for subscripts 235 // 236 ParseSubscript: function (c) { 237 if (this.string.charAt(++this.i) == "{") { 238 this.i++; this.sub += CE(this.Find("}")).Parse().replace(/^\{-\}/,"-"); 239 } else { 240 var n = this.Match(/^\d+/); 241 if (n) {this.sub += n} 242 } 243 }, 244 245 // 246 // Look for raw TeX code to include 247 // 248 ParseMath: function (c) { 249 this.FinishAtom(); 250 this.i++; this.tex += this.Find(c); 251 }, 252 253 // 254 // Look for specific macros for bonds 255 // and allow \} to have subscripts 256 // 257 ParseMacro: function (c) { 258 this.FinishAtom(); 259 this.i++; var match = this.Match(/^([a-z]+|.)/i)||" "; 260 if (match === "sbond") {this.tex += "{-}"} 261 else if (match === "dbond") {this.tex += "{=}"} 262 else if (match === "tbond") {this.tex += "{\\equiv}"} 263 else if (match === "bond") { 264 var bond = (this.Match(/^\{.*?\}/)||""); 265 bond = bond.substr(1,bond.length-2); 266 this.tex += "{"+(this.Bonds[bond]||"\\text{??}")+"}"; 267 } 268 else if (match === "{") {this.tex += "{\\{"} 269 else if (match === "}") {this.tex += "\\}}"; this.atom = true} 270 else {this.tex += c+match} 271 }, 272 273 // 274 // Ignore spaces 275 // 276 ParseSpace: function (c) {this.FinishAtom(); this.i++}, 277 278 // 279 // Pass anything else on verbatim 280 // 281 ParseOther: function (c) {this.FinishAtom(); this.tex += c; this.i++}, 282 283 // 284 // Process an arrow (looking for brackets for above and below) 285 // 286 AddArrow: function (arrow) { 287 var c = this.Match(/^[CT]\[/); 288 if (c) {this.i--; c = c.charAt(0)} 289 var above = this.GetBracket(c), below = this.GetBracket(c); 290 arrow = this.Arrows[arrow]; 291 if (above || below) { 292 if (below) {arrow += "["+below+"]"} 293 arrow += "{"+above+"}"; 294 arrow = "\\mathrel{\\x"+arrow+"}"; 295 } else { 296 arrow = "\\long"+arrow+" "; 297 } 298 this.tex += arrow; 299 }, 300 301 // 302 // Handle the super and subscripts for an atom 303 // 304 FinishAtom: function (force) { 305 if (this.sup || this.sub || this.presup || this.presub) { 306 if (!force && !this.atom) { 307 if (this.tex === "" && !this.sup && !this.sub) return; 308 if (!this.presup && !this.presub && 309 (this.tex === "" || this.tex === "{" || 310 (this.tex === "}" && this.TEX.substr(-1) === "{"))) { 311 this.presup = this.sup, this.presub = this.sub; // save for later 312 this.sub = this.sup = ""; 313 this.TEX += this.tex; this.tex = ""; 314 return; 315 } 316 } 317 if (this.sub && !this.sup) {this.sup = "\\Space{0pt}{0pt}{.2em}"} // forces subscripts to align properly 318 if ((this.presup || this.presub) && this.tex !== "{") { 319 if (!this.presup && !this.sup) {this.presup = "\\Space{0pt}{0pt}{.2em}"} 320 this.tex = "\\CEprescripts{"+(this.presub||"\\CEnone")+"}{"+(this.presup||"\\CEnone")+"}" 321 + "{"+(this.tex !== "}" ? this.tex : "")+"}" 322 + "{"+(this.sub||"\\CEnone")+"}{"+(this.sup||"\\CEnone")+"}" 323 + (this.tex === "}" ? "}" : ""); 324 this.presub = this.presup = ""; 325 } else { 326 if (this.sup) this.tex += "^{"+this.sup+"}"; 327 if (this.sub) this.tex += "_{"+this.sub+"}"; 328 } 329 this.sup = this.sub = ""; 330 } 331 this.TEX += this.tex; this.tex = ""; 332 this.atom = false; 333 }, 334 335 // 336 // Find a bracket group and handle C and T prefixes 337 // 338 GetBracket: function (c) { 339 if (this.string.charAt(this.i) !== "[") {return ""} 340 this.i++; var bracket = this.Find("]"); 341 if (c === "C") {bracket = "\\ce{"+bracket+"}"} else 342 if (c === "T") { 343 if (!bracket.match(/^\{.*\}$/)) {bracket = "{"+bracket+"}"} 344 bracket = "\\text"+bracket; 345 }; 346 return bracket; 347 }, 348 349 // 350 // Check if the string matches a regular expression 351 // and move past it if so, returning the match 352 // 353 Match: function (regex) { 354 var match = regex.exec(this.string.substr(this.i)); 355 if (match) {match = match[0]; this.i += match.length} 356 return match; 357 }, 358 359 // 360 // Find a particular character, skipping over braced groups 361 // 362 Find: function (c) { 363 var m = this.string.length, i = this.i, braces = 0; 364 while (this.i < m) { 365 var C = this.string.charAt(this.i++); 366 if (C === c && braces === 0) {return this.string.substr(i,this.i-i-1)} 367 if (C === "{") {braces++} else 368 if (C === "}") { 369 if (braces) {braces--} 370 else { 371 TEX.Error(["ExtraCloseMissingOpen","Extra close brace or missing open brace"]) 372 } 373 } 374 } 375 if (braces) {TEX.Error(["MissingCloseBrace","Missing close brace"])} 376 TEX.Error(["NoClosingChar","Can't find closing %1",c]); 377 } 378 379 }); 380 381 MathJax.Extension["TeX/mhchem"].CE = CE; 382 383 /***************************************************************************/ 384 385 TEX.Definitions.Add({ 386 macros: { 387 // 388 // Set up the macros for chemistry 389 // 390 ce: 'CE', 391 cf: 'CE', 392 cee: 'CE', 393 394 // 395 // Make these load AMSmath package (redefined below when loaded) 396 // 397 xleftrightarrow: ['Extension','AMSmath'], 398 xrightleftharpoons: ['Extension','AMSmath'], 399 xRightleftharpoons: ['Extension','AMSmath'], 400 xLeftrightharpoons: ['Extension','AMSmath'], 401 402 // FIXME: These don't work well in FF NativeMML mode 403 longrightleftharpoons: ["Macro","\\stackrel{\\textstyle{{-}\\!\\!{\\rightharpoonup}}}{\\smash{{\\leftharpoondown}\\!\\!{-}}}"], 404 longRightleftharpoons: ["Macro","\\stackrel{\\textstyle{-}\\!\\!{\\rightharpoonup}}{\\small\\smash\\leftharpoondown}"], 405 longLeftrightharpoons: ["Macro","\\stackrel{\\rightharpoonup}{{{\\leftharpoondown}\\!\\!\\textstyle{-}}}"], 406 407 // 408 // Add \hyphen used in some mhchem examples 409 // 410 hyphen: ["Macro","\\text{-}"], 411 412 // 413 // Handle prescripts and none 414 // 415 CEprescripts: "CEprescripts", 416 CEnone: "CEnone", 417 418 // 419 // Needed for \bond for the ~ forms 420 // 421 tripledash: ["Macro","\\raise3mu{\\tiny\\text{-}\\kern2mu\\text{-}\\kern2mu\\text{-}}"] 422 }, 423 424 // 425 // Needed for \bond for the ~ forms 426 // 427 environment: { 428 CEstack: ['Array',null,null,null,'r',null,"0.001em",'T',1] 429 } 430 },null,true); 431 432 if (!MathJax.Extension["TeX/AMSmath"]) { 433 TEX.Definitions.Add({ 434 macros: { 435 xrightarrow: ['Extension','AMSmath'], 436 xleftarrow: ['Extension','AMSmath'] 437 } 438 },null,true); 439 } 440 441 // 442 // These arrows need to wait until AMSmath is loaded 443 // 444 MathJax.Hub.Register.StartupHook("TeX AMSmath Ready",function () { 445 TEX.Definitions.Add({ 446 macros: { 447 // 448 // Some of these are hacks for now 449 // 450 xleftrightarrow: ['xArrow',0x2194,6,6], 451 xrightleftharpoons: ['xArrow',0x21CC,5,7], // FIXME: doesn't stretch in HTML-CSS output 452 xRightleftharpoons: ['xArrow',0x21CC,5,7], // FIXME: how should this be handled? 453 xLeftrightharpoons: ['xArrow',0x21CC,5,7] 454 } 455 },null,true); 456 }); 457 458 TEX.Parse.Augment({ 459 460 // 461 // Implements \ce and friends 462 // 463 CE: function (name) { 464 var arg = this.GetArgument(name); 465 var tex = CE(arg).Parse(); 466 this.string = tex + this.string.substr(this.i); this.i = 0; 467 }, 468 469 // 470 // Implements \CEprescripts{presub}{presup}{base}{sub}{sup} 471 // 472 CEprescripts: function (name) { 473 var presub = this.ParseArg(name), 474 presup = this.ParseArg(name), 475 base = this.ParseArg(name), 476 sub = this.ParseArg(name), 477 sup = this.ParseArg(name); 478 var MML = MathJax.ElementJax.mml; 479 this.Push(MML.mmultiscripts(base,sub,sup,MML.mprescripts(),presub,presup)); 480 }, 481 CEnone: function (name) { 482 this.Push(MathJax.ElementJax.mml.none()); 483 } 484 485 }); 486 487 // 488 // Indicate that the extension is ready 489 // 490 MathJax.Hub.Startup.signal.Post("TeX mhchem Ready"); 491 492 }); 493 494 MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/mhchem.js");