mhchem.js 16 KB

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