mhchem.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  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-2013 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. MathJax.Extension["TeX/mhchem"] = {
  27. version: "2.2"
  28. };
  29. MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
  30. var TEX = MathJax.InputJax.TeX;
  31. /*
  32. * This is the main class for handing the \ce and related commands.
  33. * Its main method is Parse() which takes the argument to \ce and
  34. * returns the corresponding TeX string.
  35. */
  36. var CE = MathJax.Object.Subclass({
  37. string: "", // the \ce string being parsed
  38. i: 0, // the current position in the string
  39. tex: "", // the processed TeX result
  40. atom: false, // last processed token is an atom
  41. sup: "", // pending superscript
  42. sub: "", // pending subscript
  43. //
  44. // Store the string when a CE object is created
  45. //
  46. Init: function (string) {this.string = string},
  47. //
  48. // These are the special characters and the methods that
  49. // handle them. All others are passed through verbatim.
  50. //
  51. ParseTable: {
  52. '-': "Minus",
  53. '+': "Plus",
  54. '(': "Open",
  55. ')': "Close",
  56. '[': "Open",
  57. ']': "Close",
  58. '<': "Less",
  59. '^': "Superscript",
  60. '_': "Subscript",
  61. '*': "Dot",
  62. '.': "Dot",
  63. '=': "Equal",
  64. '#': "Pound",
  65. '$': "Math",
  66. '\\': "Macro",
  67. ' ': "Space"
  68. },
  69. //
  70. // Basic arrow names for reactions
  71. //
  72. Arrows: {
  73. '->': "rightarrow",
  74. '<-': "leftarrow",
  75. '<->': "leftrightarrow",
  76. '<=>': "rightleftharpoons",
  77. '<=>>': "Rightleftharpoons",
  78. '<<=>': "Leftrightharpoons",
  79. '^': "uparrow",
  80. 'v': "downarrow"
  81. },
  82. //
  83. // Implementations for the various bonds
  84. // (the ~ ones are hacks that don't work well in NativeMML)
  85. //
  86. Bonds: {
  87. '-': "-",
  88. '=': "=",
  89. '#': "\\equiv",
  90. '~': "\\tripledash",
  91. '~-': "\\begin{CEstack}{}\\tripledash\\\\-\\end{CEstack}",
  92. '~=': "\\raise2mu{\\begin{CEstack}{}\\tripledash\\\\-\\\\-\\end{CEstack}}",
  93. '~--': "\\raise2mu{\\begin{CEstack}{}\\tripledash\\\\-\\\\-\\end{CEstack}}",
  94. '-~-': "\\raise2mu{\\begin{CEstack}{}-\\\\\\tripledash\\\\-\\end{CEstack}}",
  95. '...': "{\\cdot}{\\cdot}{\\cdot}",
  96. '....': "{\\cdot}{\\cdot}{\\cdot}{\\cdot}",
  97. '->': "\\rightarrow",
  98. '<-': "\\leftarrow",
  99. '??': "\\text{??}" // unknown bond
  100. },
  101. //
  102. // This converts the CE string to a TeX string.
  103. // It loops through the string and calls the proper
  104. // method depending on the ccurrent character.
  105. //
  106. Parse: function () {
  107. this.tex = ""; this.atom = false;
  108. while (this.i < this.string.length) {
  109. var c = this.string.charAt(this.i);
  110. if (c.match(/[a-z]/i)) {this.ParseLetter()}
  111. else if (c.match(/[0-9]/)) {this.ParseNumber()}
  112. else {this["Parse"+(this.ParseTable[c]||"Other")](c)}
  113. }
  114. this.FinishAtom();
  115. return this.tex;
  116. },
  117. //
  118. // Make an atom name or a down arrow
  119. //
  120. ParseLetter: function () {
  121. this.FinishAtom();
  122. if (this.Match(/^v( |$)/)) {
  123. this.tex += "{\\"+this.Arrows["v"]+"}";
  124. } else {
  125. this.tex += "\\text{"+this.Match(/^[a-z]+/i)+"}";
  126. this.atom = true;
  127. }
  128. },
  129. //
  130. // Make a number or fraction preceeding an atom,
  131. // or a subscript for an atom.
  132. //
  133. ParseNumber: function () {
  134. var n = this.Match(/^\d+/);
  135. if (this.atom && !this.sub) {
  136. this.sub = n;
  137. } else {
  138. this.FinishAtom();
  139. var match = this.Match(/^\/\d+/);
  140. if (match) {
  141. var frac = "\\frac{"+n+"}{"+match.substr(1)+"}";
  142. this.tex += "\\mathchoice{\\textstyle"+frac+"}{"+frac+"}{"+frac+"}{"+frac+"}";
  143. } else {
  144. this.tex += n;
  145. if (this.i < this.string.length) {this.tex += "\\,"}
  146. }
  147. }
  148. },
  149. //
  150. // Make a superscript minus, or an arrow, or a single bond.
  151. //
  152. ParseMinus: function (c) {
  153. if (this.atom && (this.i === this.string.length-1 || this.string.charAt(this.i+1) === " ")) {
  154. this.sup += c;
  155. } else {
  156. this.FinishAtom();
  157. if (this.string.substr(this.i,2) === "->") {this.i += 2; this.AddArrow("->"); return}
  158. else {this.tex += "{-}"}
  159. }
  160. this.i++;
  161. },
  162. //
  163. // Make a superscript plus, or pass it through
  164. //
  165. ParsePlus: function (c) {
  166. if (this.atom) {this.sup += c} else {this.FinishAtom(); this.tex += c}
  167. this.i++;
  168. },
  169. //
  170. // Handle dots and double or triple bonds
  171. //
  172. ParseDot: function (c) {this.FinishAtom(); this.tex += "\\cdot "; this.i++},
  173. ParseEqual: function (c) {this.FinishAtom(); this.tex += "{=}"; this.i++},
  174. ParsePound: function (c) {this.FinishAtom(); this.tex += "{\\equiv}"; this.i++},
  175. //
  176. // Look for (v) or (^), or pass it through
  177. //
  178. ParseOpen: function (c) {
  179. this.FinishAtom();
  180. var match = this.Match(/^\([v^]\)/);
  181. if (match) {this.tex += "{\\"+this.Arrows[match.charAt(1)]+"}"}
  182. else {this.tex += "{"+c; this.i++}
  183. },
  184. //
  185. // Allow ) and ] to get super- and subscripts
  186. //
  187. ParseClose: function (c) {this.FinishAtom(); this.atom = true; this.tex += c+"}"; this.i++},
  188. //
  189. // Make the proper arrow
  190. //
  191. ParseLess: function (c) {
  192. this.FinishAtom();
  193. var arrow = this.Match(/^(<->?|<=>>?|<<=>)/);
  194. if (!arrow) {this.tex += c; this.i++} else {this.AddArrow(arrow)}
  195. },
  196. //
  197. // Look for a superscript, or an up arrow
  198. //
  199. ParseSuperscript: function (c) {
  200. c = this.string.charAt(++this.i);
  201. if (c === "{") {
  202. this.i++; var m = this.Find("}");
  203. if (m === "-.") {this.sup += "{-}{\\cdot}"}
  204. else if (m) {this.sup += CE(m).Parse().replace(/^\{-\}/,"-")}
  205. } else if (c === " " || c === "") {
  206. this.tex += "{\\"+this.Arrows["^"]+"}"; this.i++;
  207. } else {
  208. var n = this.Match(/^(\d+|-\.)/);
  209. if (n) {this.sup += n}
  210. }
  211. },
  212. //
  213. // Look for subscripts
  214. //
  215. ParseSubscript: function (c) {
  216. if (this.string.charAt(++this.i) == "{") {
  217. this.i++; this.sub += CE(this.Find("}")).Parse().replace(/^\{-\}/,"-");
  218. } else {
  219. var n = this.Match(/^\d+/);
  220. if (n) {this.sub += n}
  221. }
  222. },
  223. //
  224. // Look for raw TeX code to include
  225. //
  226. ParseMath: function (c) {
  227. this.FinishAtom();
  228. this.i++; this.tex += this.Find(c);
  229. },
  230. //
  231. // Look for specific macros for bonds
  232. // and allow \} to have subscripts
  233. //
  234. ParseMacro: function (c) {
  235. this.FinishAtom();
  236. this.i++; var match = this.Match(/^([a-z]+|.)/i)||" ";
  237. if (match === "sbond") {this.tex += "{-}"}
  238. else if (match === "dbond") {this.tex += "{=}"}
  239. else if (match === "tbond") {this.tex += "{\\equiv}"}
  240. else if (match === "bond") {
  241. var bond = (this.Match(/^\{.*?\}/)||"");
  242. bond = bond.substr(1,bond.length-2);
  243. this.tex += "{"+(this.Bonds[bond]||"\\text{??}")+"}";
  244. }
  245. else if (match === "{") {this.tex += "{\\{"}
  246. else if (match === "}") {this.tex += "\\}}"; this.atom = true}
  247. else {this.tex += c+match}
  248. },
  249. //
  250. // Ignore spaces
  251. //
  252. ParseSpace: function (c) {this.FinishAtom(); this.i++},
  253. //
  254. // Pass anything else on verbatim
  255. //
  256. ParseOther: function (c) {this.FinishAtom(); this.tex += c; this.i++},
  257. //
  258. // Process an arrow (looking for brackets for above and below)
  259. //
  260. AddArrow: function (arrow) {
  261. var c = this.Match(/^[CT]\[/);
  262. if (c) {this.i--; c = c.charAt(0)}
  263. var above = this.GetBracket(c), below = this.GetBracket(c);
  264. arrow = this.Arrows[arrow];
  265. if (above || below) {
  266. if (below) {arrow += "["+below+"]"}
  267. arrow += "{"+above+"}";
  268. arrow = "\\mathrel{\\x"+arrow+"}";
  269. } else {
  270. arrow = "\\long"+arrow+" ";
  271. }
  272. this.tex += arrow;
  273. },
  274. //
  275. // Handle the super and subscripts for an atom
  276. //
  277. FinishAtom: function () {
  278. if (this.sup || this.sub) {
  279. if (this.sup && this.sub && !this.atom) {
  280. //
  281. // right-justify super- and subscripts when they are before the atom
  282. //
  283. var sup = this.sup, sub = this.sub;
  284. if (!sup.match(/\d/)) {sup += "\\vphantom{0}"} // force common heights
  285. if (!sub.match(/\d/)) {sub += "\\vphantom{0}"}
  286. this.tex += "\\raise 1pt{\\scriptstyle\\begin{CEscriptstack}"+sup+"\\\\"+
  287. sub+"\\end{CEscriptstack}}\\kern-.125em ";
  288. } else {
  289. if (!this.sup) {this.sup = "\\Space{0pt}{0pt}{.2em}"} // forces subscripts to align properly
  290. this.tex += "^{"+this.sup+"}_{"+this.sub+"}";
  291. }
  292. this.sup = this.sub = "";
  293. }
  294. this.atom = false;
  295. },
  296. //
  297. // Find a bracket group and handle C and T prefixes
  298. //
  299. GetBracket: function (c) {
  300. if (this.string.charAt(this.i) !== "[") {return ""}
  301. this.i++; var bracket = this.Find("]");
  302. if (c === "C") {bracket = "\\ce{"+bracket+"}"} else
  303. if (c === "T") {
  304. if (!bracket.match(/^\{.*\}$/)) {bracket = "{"+bracket+"}"}
  305. bracket = "\\text"+bracket;
  306. };
  307. return bracket;
  308. },
  309. //
  310. // Check if the string matches a regular expression
  311. // and move past it if so, returning the match
  312. //
  313. Match: function (regex) {
  314. var match = regex.exec(this.string.substr(this.i));
  315. if (match) {match = match[0]; this.i += match.length}
  316. return match;
  317. },
  318. //
  319. // Find a particular character, skipping over braced groups
  320. //
  321. Find: function (c) {
  322. var m = this.string.length, i = this.i, braces = 0;
  323. while (this.i < m) {
  324. var C = this.string.charAt(this.i++);
  325. if (C === c && braces === 0) {return this.string.substr(i,this.i-i-1)}
  326. if (C === "{") {braces++} else
  327. if (C === "}") {
  328. if (braces) {braces--}
  329. else {
  330. TEX.Error(["ExtraCloseMissingOpen","Extra close brace or missing open brace"])
  331. }
  332. }
  333. }
  334. if (braces) {TEX.Error(["MissingCloseBrace","Missing close brace"])}
  335. TEX.Error(["NoClosingChar","Can't find closing %1",c]);
  336. }
  337. });
  338. MathJax.Extension["TeX/mhchem"].CE = CE;
  339. /***************************************************************************/
  340. TEX.Definitions.Add({
  341. macros: {
  342. //
  343. // Set up the macros for chemistry
  344. //
  345. ce: 'CE',
  346. cf: 'CE',
  347. cee: 'CE',
  348. //
  349. // Make these load AMSmath package (redefined below when loaded)
  350. //
  351. xleftrightarrow: ['Extension','AMSmath'],
  352. xrightleftharpoons: ['Extension','AMSmath'],
  353. xRightleftharpoons: ['Extension','AMSmath'],
  354. xLeftrightharpoons: ['Extension','AMSmath'],
  355. // FIXME: These don't work well in FF NativeMML mode
  356. longrightleftharpoons: ["Macro","\\stackrel{\\textstyle{{-}\\!\\!{\\rightharpoonup}}}{\\smash{{\\leftharpoondown}\\!\\!{-}}}"],
  357. longRightleftharpoons: ["Macro","\\stackrel{\\textstyle{-}\\!\\!{\\rightharpoonup}}{\\small\\smash\\leftharpoondown}"],
  358. longLeftrightharpoons: ["Macro","\\stackrel{\\rightharpoonup}{{{\\leftharpoondown}\\!\\!\\textstyle{-}}}"],
  359. //
  360. // Add \hyphen used in some mhchem examples
  361. //
  362. hyphen: ["Macro","\\text{-}"],
  363. //
  364. // Needed for \bond for the ~ forms
  365. //
  366. tripledash: ["Macro","\\raise3mu{\\tiny\\text{-}\\kern2mu\\text{-}\\kern2mu\\text{-}}"]
  367. },
  368. //
  369. // Needed for \bond for the ~ forms
  370. //
  371. environment: {
  372. CEstack: ['Array',null,null,null,'r',null,"0.001em",'T',1],
  373. CEscriptstack: ['Array',null,null,null,'r',null,"0.2em",'S',1]
  374. }
  375. },null,true);
  376. if (!MathJax.Extension["TeX/AMSmath"]) {
  377. TEX.Definitions.Add({
  378. macros: {
  379. xrightarrow: ['Extension','AMSmath'],
  380. xleftarrow: ['Extension','AMSmath']
  381. }
  382. },null,true);
  383. }
  384. //
  385. // These arrows need to wait until AMSmath is loaded
  386. //
  387. MathJax.Hub.Register.StartupHook("TeX AMSmath Ready",function () {
  388. TEX.Definitions.Add({
  389. macros: {
  390. //
  391. // Some of these are hacks for now
  392. //
  393. xleftrightarrow: ['xArrow',0x2194,6,6],
  394. xrightleftharpoons: ['xArrow',0x21CC,5,7], // FIXME: doesn't stretch in HTML-CSS output
  395. xRightleftharpoons: ['xArrow',0x21CC,5,7], // FIXME: how should this be handled?
  396. xLeftrightharpoons: ['xArrow',0x21CC,5,7]
  397. }
  398. },null,true);
  399. });
  400. TEX.Parse.Augment({
  401. //
  402. // Implements \ce and friends
  403. //
  404. CE: function (name) {
  405. var arg = this.GetArgument(name);
  406. var tex = CE(arg).Parse();
  407. this.string = tex + this.string.substr(this.i); this.i = 0;
  408. }
  409. });
  410. //
  411. // Indicate that the extension is ready
  412. //
  413. MathJax.Hub.Startup.signal.Post("TeX mhchem Ready");
  414. });
  415. MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/mhchem.js");