mhchem.js 68 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770
  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-2015 The MathJax Consortium
  13. * Copyright (c) 2015-2018 Martin Hensel
  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. // Coding Style
  29. // - use '' for identifiers that can by minified/uglified
  30. // - use "" for strings that need to stay untouched
  31. MathJax.Extension["TeX/mhchem"] = {
  32. version: "3.3.0"
  33. };
  34. MathJax.Hub.Register.StartupHook("TeX Jax Ready", function () {
  35. var TEX = MathJax.InputJax.TeX;
  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. var CE = MathJax.Object.Subclass({
  42. string: "", // the \ce string being parsed
  43. //
  44. // Store the string when a CE object is created
  45. //
  46. Init: function (string) { this.string = string; },
  47. //
  48. // This converts the CE string to a TeX string.
  49. //
  50. Parse: function (stateMachine) {
  51. try {
  52. return texify.go(mhchemParser.go(this.string, stateMachine));
  53. } catch (ex) {
  54. TEX.Error(ex);
  55. }
  56. }
  57. });
  58. //
  59. // Core parser for mhchem syntax (recursive)
  60. //
  61. /** @type {MhchemParser} */
  62. var mhchemParser = {
  63. //
  64. // Parses mchem \ce syntax
  65. //
  66. // Call like
  67. // go("H2O");
  68. //
  69. go: function (input, stateMachine) {
  70. if (!input) { return []; }
  71. if (stateMachine === undefined) { stateMachine = 'ce'; }
  72. var state = '0';
  73. //
  74. // String buffers for parsing:
  75. //
  76. // buffer.a == amount
  77. // buffer.o == element
  78. // buffer.b == left-side superscript
  79. // buffer.p == left-side subscript
  80. // buffer.q == right-side subscript
  81. // buffer.d == right-side superscript
  82. //
  83. // buffer.r == arrow
  84. // buffer.rdt == arrow, script above, type
  85. // buffer.rd == arrow, script above, content
  86. // buffer.rqt == arrow, script below, type
  87. // buffer.rq == arrow, script below, content
  88. //
  89. // buffer.text_
  90. // buffer.rm
  91. // etc.
  92. //
  93. // buffer.parenthesisLevel == int, starting at 0
  94. // buffer.sb == bool, space before
  95. // buffer.beginsWithBond == bool
  96. //
  97. // These letters are also used as state names.
  98. //
  99. // Other states:
  100. // 0 == begin of main part (arrow/operator unlikely)
  101. // 1 == next entity
  102. // 2 == next entity (arrow/operator unlikely)
  103. // 3 == next atom
  104. // c == macro
  105. //
  106. /** @type {Buffer} */
  107. var buffer = {};
  108. buffer['parenthesisLevel'] = 0;
  109. input = input.replace(/\n/g, " ");
  110. input = input.replace(/[\u2212\u2013\u2014\u2010]/g, "-");
  111. input = input.replace(/[\u2026]/g, "...");
  112. //
  113. // Looks through mhchemParser.transitions, to execute a matching action
  114. // (recursive)
  115. //
  116. var lastInput;
  117. var watchdog = 10;
  118. /** @type {ParserOutput[]} */
  119. var output = [];
  120. while (true) {
  121. if (lastInput !== input) {
  122. watchdog = 10;
  123. lastInput = input;
  124. } else {
  125. watchdog--;
  126. }
  127. //
  128. // Find actions in transition table
  129. //
  130. var machine = mhchemParser.stateMachines[stateMachine];
  131. var t = machine.transitions[state] || machine.transitions['*'];
  132. iterateTransitions:
  133. for (var i=0; i<t.length; i++) {
  134. var matches = mhchemParser.patterns.match_(t[i].pattern, input);
  135. if (matches) {
  136. //
  137. // Execute actions
  138. //
  139. var task = t[i].task;
  140. for (var iA=0; iA<task.action_.length; iA++) {
  141. var o;
  142. //
  143. // Find and execute action
  144. //
  145. if (machine.actions[task.action_[iA].type_]) {
  146. o = machine.actions[task.action_[iA].type_](buffer, matches.match_, task.action_[iA].option);
  147. } else if (mhchemParser.actions[task.action_[iA].type_]) {
  148. o = mhchemParser.actions[task.action_[iA].type_](buffer, matches.match_, task.action_[iA].option);
  149. } else {
  150. throw ["MhchemBugA", "mhchem bug A. Please report. (" + task.action_[iA].type_ + ")"]; // Trying to use non-existing action
  151. }
  152. //
  153. // Add output
  154. //
  155. mhchemParser.concatArray(output, o);
  156. }
  157. //
  158. // Set next state,
  159. // Shorten input,
  160. // Continue with next character
  161. // (= apply only one transition per position)
  162. //
  163. state = task.nextState || state;
  164. if (input.length > 0) {
  165. if (!task.revisit) {
  166. input = matches.remainder;
  167. }
  168. if (!task.toContinue) {
  169. break iterateTransitions;
  170. }
  171. } else {
  172. return output;
  173. }
  174. }
  175. }
  176. //
  177. // Prevent infinite loop
  178. //
  179. if (watchdog <= 0) {
  180. throw ["MhchemBugU", "mhchem bug U. Please report."]; // Unexpected character
  181. }
  182. }
  183. },
  184. concatArray: function (a, b) {
  185. if (b) {
  186. if (Array.isArray(b)) {
  187. for (var iB=0; iB<b.length; iB++) {
  188. a.push(b[iB]);
  189. }
  190. } else {
  191. a.push(b);
  192. }
  193. }
  194. },
  195. patterns: {
  196. //
  197. // Matching patterns
  198. // either regexps or function that return null or {match_:"a", remainder:"bc"}
  199. //
  200. patterns: {
  201. // property names must not look like integers ("2") for correct property traversal order, later on
  202. 'empty': /^$/,
  203. 'else': /^./,
  204. 'else2': /^./,
  205. 'space': /^\s/,
  206. 'space A': /^\s(?=[A-Z\\$])/,
  207. 'space$': /^\s$/,
  208. 'a-z': /^[a-z]/,
  209. 'x': /^x/,
  210. 'x$': /^x$/,
  211. 'i$': /^i$/,
  212. 'letters': /^(?:[a-zA-Z\u03B1-\u03C9\u0391-\u03A9?@]|(?:\\(?:alpha|beta|gamma|delta|epsilon|zeta|eta|theta|iota|kappa|lambda|mu|nu|xi|omicron|pi|rho|sigma|tau|upsilon|phi|chi|psi|omega|Gamma|Delta|Theta|Lambda|Xi|Pi|Sigma|Upsilon|Phi|Psi|Omega)(?:\s+|\{\}|(?![a-zA-Z]))))+/,
  213. '\\greek': /^\\(?:alpha|beta|gamma|delta|epsilon|zeta|eta|theta|iota|kappa|lambda|mu|nu|xi|omicron|pi|rho|sigma|tau|upsilon|phi|chi|psi|omega|Gamma|Delta|Theta|Lambda|Xi|Pi|Sigma|Upsilon|Phi|Psi|Omega)(?:\s+|\{\}|(?![a-zA-Z]))/,
  214. 'one lowercase latin letter $': /^(?:([a-z])(?:$|[^a-zA-Z]))$/,
  215. '$one lowercase latin letter$ $': /^\$(?:([a-z])(?:$|[^a-zA-Z]))\$$/,
  216. 'one lowercase greek letter $': /^(?:\$?[\u03B1-\u03C9]\$?|\$?\\(?:alpha|beta|gamma|delta|epsilon|zeta|eta|theta|iota|kappa|lambda|mu|nu|xi|omicron|pi|rho|sigma|tau|upsilon|phi|chi|psi|omega)\s*\$?)(?:\s+|\{\}|(?![a-zA-Z]))$/,
  217. 'digits': /^[0-9]+/,
  218. '-9.,9': /^[+\-]?(?:[0-9]+(?:[,.][0-9]+)?|[0-9]*(?:\.[0-9]+))/,
  219. '-9.,9 no missing 0': /^[+\-]?[0-9]+(?:[.,][0-9]+)?/,
  220. '(-)(9.,9)(e)(99)': function (input) {
  221. var m = input.match(/^(\+\-|\+\/\-|\+|\-|\\pm\s?)?([0-9]+(?:[,.][0-9]+)?|[0-9]*(?:\.[0-9]+))?(\((?:[0-9]+(?:[,.][0-9]+)?|[0-9]*(?:\.[0-9]+))\))?(?:([eE]|\s*(\*|x|\\times|\u00D7)\s*10\^)([+\-]?[0-9]+|\{[+\-]?[0-9]+\}))?/);
  222. if (m && m[0]) {
  223. return { match_: m.splice(1), remainder: input.substr(m[0].length) };
  224. }
  225. return null;
  226. },
  227. '(-)(9)^(-9)': function (input) {
  228. var m = input.match(/^(\+\-|\+\/\-|\+|\-|\\pm\s?)?([0-9]+(?:[,.][0-9]+)?|[0-9]*(?:\.[0-9]+)?)\^([+\-]?[0-9]+|\{[+\-]?[0-9]+\})/);
  229. if (m && m[0]) {
  230. return { match_: m.splice(1), remainder: input.substr(m[0].length) };
  231. }
  232. return null;
  233. },
  234. 'state of aggregation $': function (input) { // ... or crystal system
  235. var a = mhchemParser.patterns.findObserveGroups(input, "", /^\([a-z]{1,3}(?=[\),])/, ")", ""); // (aq), (aq,$\infty$), (aq, sat)
  236. if (a && a.remainder.match(/^($|[\s,;\)\]\}])/)) { return a; } // AND end of 'phrase'
  237. var m = input.match(/^(?:\((?:\\ca\s?)?\$[amothc]\$\))/); // OR crystal system ($o$) (\ca$c$)
  238. if (m) {
  239. return { match_: m[0], remainder: input.substr(m[0].length) };
  240. }
  241. return null;
  242. },
  243. '_{(state of aggregation)}$': /^_\{(\([a-z]{1,3}\))\}/,
  244. '{[(': /^(?:\\\{|\[|\()/,
  245. ')]}': /^(?:\)|\]|\\\})/,
  246. ', ': /^[,;]\s*/,
  247. ',': /^[,;]/,
  248. '.': /^[.]/,
  249. '. ': /^([.\u22C5\u00B7\u2022])\s*/,
  250. '...': /^\.\.\.(?=$|[^.])/,
  251. '* ': /^([*])\s*/,
  252. '^{(...)}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "^{", "", "", "}"); },
  253. '^($...$)': function (input) { return mhchemParser.patterns.findObserveGroups(input, "^", "$", "$", ""); },
  254. '^a': /^\^([0-9]+|[^\\_])/,
  255. '^\\x{}{}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "^", /^\\[a-zA-Z]+\{/, "}", "", "", "{", "}", "", true); },
  256. '^\\x{}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "^", /^\\[a-zA-Z]+\{/, "}", ""); },
  257. '^\\x': /^\^(\\[a-zA-Z]+)\s*/,
  258. '^(-1)': /^\^(-?\d+)/,
  259. '\'': /^'/,
  260. '_{(...)}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "_{", "", "", "}"); },
  261. '_($...$)': function (input) { return mhchemParser.patterns.findObserveGroups(input, "_", "$", "$", ""); },
  262. '_9': /^_([+\-]?[0-9]+|[^\\])/,
  263. '_\\x{}{}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "_", /^\\[a-zA-Z]+\{/, "}", "", "", "{", "}", "", true); },
  264. '_\\x{}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "_", /^\\[a-zA-Z]+\{/, "}", ""); },
  265. '_\\x': /^_(\\[a-zA-Z]+)\s*/,
  266. '^_': /^(?:\^(?=_)|\_(?=\^)|[\^_]$)/,
  267. '{}': /^\{\}/,
  268. '{...}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "", "{", "}", ""); },
  269. '{(...)}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "{", "", "", "}"); },
  270. '$...$': function (input) { return mhchemParser.patterns.findObserveGroups(input, "", "$", "$", ""); },
  271. '${(...)}$': function (input) { return mhchemParser.patterns.findObserveGroups(input, "${", "", "", "}$"); },
  272. '$(...)$': function (input) { return mhchemParser.patterns.findObserveGroups(input, "$", "", "", "$"); },
  273. '=<>': /^[=<>]/,
  274. '#': /^[#\u2261]/,
  275. '+': /^\+/,
  276. '-$': /^-(?=[\s_},;\]/]|$|\([a-z]+\))/, // -space -, -; -] -/ -$ -state-of-aggregation
  277. '-9': /^-(?=[0-9])/,
  278. '- orbital overlap': /^-(?=(?:[spd]|sp)(?:$|[\s,;\)\]\}]))/,
  279. '-': /^-/,
  280. 'pm-operator': /^(?:\\pm|\$\\pm\$|\+-|\+\/-)/,
  281. 'operator': /^(?:\+|(?:[\-=<>]|<<|>>|\\approx|\$\\approx\$)(?=\s|$|-?[0-9]))/,
  282. 'arrowUpDown': /^(?:v|\(v\)|\^|\(\^\))(?=$|[\s,;\)\]\}])/,
  283. '\\bond{(...)}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\bond{", "", "", "}"); },
  284. '->': /^(?:<->|<-->|->|<-|<=>>|<<=>|<=>|[\u2192\u27F6\u21CC])/,
  285. 'CMT': /^[CMT](?=\[)/,
  286. '[(...)]': function (input) { return mhchemParser.patterns.findObserveGroups(input, "[", "", "", "]"); },
  287. '1st-level escape': /^(&|\\\\|\\hline)\s*/,
  288. '\\,': /^(?:\\[,\ ;:])/, // \\x - but output no space before
  289. '\\x{}{}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "", /^\\[a-zA-Z]+\{/, "}", "", "", "{", "}", "", true); },
  290. '\\x{}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "", /^\\[a-zA-Z]+\{/, "}", ""); },
  291. '\\ca': /^\\ca(?:\s+|(?![a-zA-Z]))/,
  292. '\\x': /^(?:\\[a-zA-Z]+\s*|\\[_&{}%])/,
  293. 'orbital': /^(?:[0-9]{1,2}[spdfgh]|[0-9]{0,2}sp)(?=$|[^a-zA-Z])/, // only those with numbers in front, because the others will be formatted correctly anyway
  294. 'others': /^[\/~|]/,
  295. '\\frac{(...)}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\frac{", "", "", "}", "{", "", "", "}"); },
  296. '\\overset{(...)}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\overset{", "", "", "}", "{", "", "", "}"); },
  297. '\\underset{(...)}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\underset{", "", "", "}", "{", "", "", "}"); },
  298. '\\underbrace{(...)}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\underbrace{", "", "", "}_", "{", "", "", "}"); },
  299. '\\color{(...)}0': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\color{", "", "", "}"); },
  300. '\\color{(...)}{(...)}1': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\color{", "", "", "}", "{", "", "", "}"); },
  301. '\\color(...){(...)}2': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\color", "\\", "", /^(?=\{)/, "{", "", "", "}"); },
  302. '\\ce{(...)}': function (input) { return mhchemParser.patterns.findObserveGroups(input, "\\ce{", "", "", "}"); },
  303. 'oxidation$': /^(?:[+-][IVX]+|\\pm\s*0|\$\\pm\$\s*0)$/,
  304. 'd-oxidation$': /^(?:[+-]?\s?[IVX]+|\\pm\s*0|\$\\pm\$\s*0)$/, // 0 could be oxidation or charge
  305. 'roman numeral': /^[IVX]+/,
  306. '1/2$': /^[+\-]?(?:[0-9]+|\$[a-z]\$|[a-z])\/[0-9]+(?:\$[a-z]\$|[a-z])?$/,
  307. 'amount': function (input) {
  308. var match;
  309. // e.g. 2, 0.5, 1/2, -2, n/2, +; $a$ could be added later in parsing
  310. match = input.match(/^(?:(?:(?:\([+\-]?[0-9]+\/[0-9]+\)|[+\-]?(?:[0-9]+|\$[a-z]\$|[a-z])\/[0-9]+|[+\-]?[0-9]+[.,][0-9]+|[+\-]?\.[0-9]+|[+\-]?[0-9]+)(?:[a-z](?=\s*[A-Z]))?)|[+\-]?[a-z](?=\s*[A-Z])|\+(?!\s))/);
  311. if (match) {
  312. return { match_: match[0], remainder: input.substr(match[0].length) };
  313. }
  314. var a = mhchemParser.patterns.findObserveGroups(input, "", "$", "$", "");
  315. if (a) { // e.g. $2n-1$, $-$
  316. match = a.match_.match(/^\$(?:\(?[+\-]?(?:[0-9]*[a-z]?[+\-])?[0-9]*[a-z](?:[+\-][0-9]*[a-z]?)?\)?|\+|-)\$$/);
  317. if (match) {
  318. return { match_: match[0], remainder: input.substr(match[0].length) };
  319. }
  320. }
  321. return null;
  322. },
  323. 'amount2': function (input) { return this['amount'](input); },
  324. '(KV letters),': /^(?:[A-Z][a-z]{0,2}|i)(?=,)/,
  325. 'formula$': function (input) {
  326. if (input.match(/^\([a-z]+\)$/)) { return null; } // state of aggregation = no formula
  327. var match = input.match(/^(?:[a-z]|(?:[0-9\ \+\-\,\.\(\)]+[a-z])+[0-9\ \+\-\,\.\(\)]*|(?:[a-z][0-9\ \+\-\,\.\(\)]+)+[a-z]?)$/);
  328. if (match) {
  329. return { match_: match[0], remainder: input.substr(match[0].length) };
  330. }
  331. return null;
  332. },
  333. 'uprightEntities': /^(?:pH|pOH|pC|pK|iPr|iBu)(?=$|[^a-zA-Z])/,
  334. '/': /^\s*(\/)\s*/,
  335. '//': /^\s*(\/\/)\s*/,
  336. '*': /^\s*[*.]\s*/
  337. },
  338. findObserveGroups: function (input, begExcl, begIncl, endIncl, endExcl, beg2Excl, beg2Incl, end2Incl, end2Excl, combine) {
  339. /** @type {{(input: string, pattern: string | RegExp): string | string[] | null;}} */
  340. var _match = function (input, pattern) {
  341. if (typeof pattern === "string") {
  342. if (input.indexOf(pattern) !== 0) { return null; }
  343. return pattern;
  344. } else {
  345. var match = input.match(pattern);
  346. if (!match) { return null; }
  347. return match[0];
  348. }
  349. };
  350. /** @type {{(input: string, i: number, endChars: string | RegExp): {endMatchBegin: number, endMatchEnd: number} | null;}} */
  351. var _findObserveGroups = function (input, i, endChars) {
  352. var braces = 0;
  353. while (i < input.length) {
  354. var a = input.charAt(i);
  355. var match = _match(input.substr(i), endChars);
  356. if (match !== null && braces === 0) {
  357. return { endMatchBegin: i, endMatchEnd: i + match.length };
  358. } else if (a === "{") {
  359. braces++;
  360. } else if (a === "}") {
  361. if (braces === 0) {
  362. throw ["ExtraCloseMissingOpen", "Extra close brace or missing open brace"];
  363. } else {
  364. braces--;
  365. }
  366. }
  367. i++;
  368. }
  369. if (braces > 0) {
  370. return null;
  371. }
  372. return null;
  373. };
  374. var match = _match(input, begExcl);
  375. if (match === null) { return null; }
  376. input = input.substr(match.length);
  377. match = _match(input, begIncl);
  378. if (match === null) { return null; }
  379. var e = _findObserveGroups(input, match.length, endIncl || endExcl);
  380. if (e === null) { return null; }
  381. var match1 = input.substring(0, (endIncl ? e.endMatchEnd : e.endMatchBegin));
  382. if (!(beg2Excl || beg2Incl)) {
  383. return {
  384. match_: match1,
  385. remainder: input.substr(e.endMatchEnd)
  386. };
  387. } else {
  388. var group2 = this.findObserveGroups(input.substr(e.endMatchEnd), beg2Excl, beg2Incl, end2Incl, end2Excl);
  389. if (group2 === null) { return null; }
  390. /** @type {string[]} */
  391. var matchRet = [match1, group2.match_];
  392. return {
  393. match_: (combine ? matchRet.join("") : matchRet),
  394. remainder: group2.remainder
  395. };
  396. }
  397. },
  398. //
  399. // Matching function
  400. // e.g. match("a", input) will look for the regexp called "a" and see if it matches
  401. // returns null or {match_:"a", remainder:"bc"}
  402. //
  403. match_: function (m, input) {
  404. var pattern = mhchemParser.patterns.patterns[m];
  405. if (pattern === undefined) {
  406. throw ["MhchemBugP", "mhchem bug P. Please report. (" + m + ")"]; // Trying to use non-existing pattern
  407. } else if (typeof pattern === "function") {
  408. return mhchemParser.patterns.patterns[m](input); // cannot use cached var pattern here, because some pattern functions need this===mhchemParser
  409. } else { // RegExp
  410. var match = input.match(pattern);
  411. if (match) {
  412. var mm;
  413. if (match[2]) {
  414. mm = [ match[1], match[2] ];
  415. } else if (match[1]) {
  416. mm = match[1];
  417. } else {
  418. mm = match[0];
  419. }
  420. return { match_: mm, remainder: input.substr(match[0].length) };
  421. }
  422. return null;
  423. }
  424. }
  425. },
  426. //
  427. // Generic state machine actions
  428. //
  429. actions: {
  430. 'a=': function (buffer, m) { buffer.a = (buffer.a || "") + m; },
  431. 'b=': function (buffer, m) { buffer.b = (buffer.b || "") + m; },
  432. 'p=': function (buffer, m) { buffer.p = (buffer.p || "") + m; },
  433. 'o=': function (buffer, m) { buffer.o = (buffer.o || "") + m; },
  434. 'q=': function (buffer, m) { buffer.q = (buffer.q || "") + m; },
  435. 'd=': function (buffer, m) { buffer.d = (buffer.d || "") + m; },
  436. 'rm=': function (buffer, m) { buffer.rm = (buffer.rm || "") + m; },
  437. 'text=': function (buffer, m) { buffer.text_ = (buffer.text_ || "") + m; },
  438. 'insert': function (buffer, m, a) { return { type_: a }; },
  439. 'insert+p1': function (buffer, m, a) { return { type_: a, p1: m }; },
  440. 'insert+p1+p2': function (buffer, m, a) { return { type_: a, p1: m[0], p2: m[1] }; },
  441. 'copy': function (buffer, m) { return m; },
  442. 'rm': function (buffer, m) { return { type_: 'rm', p1: m || ""}; },
  443. 'text': function (buffer, m) { return mhchemParser.go(m, 'text'); },
  444. '{text}': function (buffer, m) {
  445. var ret = [ "{" ];
  446. mhchemParser.concatArray(ret, mhchemParser.go(m, 'text'));
  447. ret.push("}");
  448. return ret;
  449. },
  450. 'tex-math': function (buffer, m) { return mhchemParser.go(m, 'tex-math'); },
  451. 'tex-math tight': function (buffer, m) { return mhchemParser.go(m, 'tex-math tight'); },
  452. 'bond': function (buffer, m, k) { return { type_: 'bond', kind_: k || m }; },
  453. 'color0-output': function (buffer, m) { return { type_: 'color0', color: m[0] }; },
  454. 'ce': function (buffer, m) { return mhchemParser.go(m); },
  455. '1/2': function (buffer, m) {
  456. /** @type {ParserOutput[]} */
  457. var ret = [];
  458. if (m.match(/^[+\-]/)) {
  459. ret.push(m.substr(0, 1));
  460. m = m.substr(1);
  461. }
  462. var n = m.match(/^([0-9]+|\$[a-z]\$|[a-z])\/([0-9]+)(\$[a-z]\$|[a-z])?$/);
  463. n[1] = n[1].replace(/\$/g, "");
  464. ret.push({ type_: 'frac', p1: n[1], p2: n[2] });
  465. if (n[3]) {
  466. n[3] = n[3].replace(/\$/g, "");
  467. ret.push({ type_: 'tex-math', p1: n[3] });
  468. }
  469. return ret;
  470. },
  471. '9,9': function (buffer, m) { return mhchemParser.go(m, '9,9'); }
  472. },
  473. //
  474. // createTransitions
  475. // convert { 'letter': { 'state': { action_: 'output' } } } to { 'state' => [ { pattern: 'letter', task: { action_: [{type_: 'output'}] } } ] }
  476. // with expansion of 'a|b' to 'a' and 'b' (at 2 places)
  477. //
  478. createTransitions: function (o) {
  479. var pattern, state;
  480. /** @type {string[]} */
  481. var stateArray;
  482. var i;
  483. //
  484. // 1. Collect all states
  485. //
  486. /** @type {Transitions} */
  487. var transitions = {};
  488. for (pattern in o) {
  489. for (state in o[pattern]) {
  490. stateArray = state.split("|");
  491. o[pattern][state].stateArray = stateArray;
  492. for (i=0; i<stateArray.length; i++) {
  493. transitions[stateArray[i]] = [];
  494. }
  495. }
  496. }
  497. //
  498. // 2. Fill states
  499. //
  500. for (pattern in o) {
  501. for (state in o[pattern]) {
  502. stateArray = o[pattern][state].stateArray || [];
  503. for (i=0; i<stateArray.length; i++) {
  504. //
  505. // 2a. Normalize actions into array: 'text=' ==> [{type_:'text='}]
  506. // (Note to myself: Resolving the function here would be problematic. It would need .bind (for *this*) and currying (for *option*).)
  507. //
  508. /** @type {any} */
  509. var p = o[pattern][state];
  510. if (p.action_) {
  511. p.action_ = [].concat(p.action_);
  512. for (var k=0; k<p.action_.length; k++) {
  513. if (typeof p.action_[k] === "string") {
  514. p.action_[k] = { type_: p.action_[k] };
  515. }
  516. }
  517. } else {
  518. p.action_ = [];
  519. }
  520. //
  521. // 2.b Multi-insert
  522. //
  523. var patternArray = pattern.split("|");
  524. for (var j=0; j<patternArray.length; j++) {
  525. if (stateArray[i] === '*') { // insert into all
  526. for (var t in transitions) {
  527. transitions[t].push({ pattern: patternArray[j], task: p });
  528. }
  529. } else {
  530. transitions[stateArray[i]].push({ pattern: patternArray[j], task: p });
  531. }
  532. }
  533. }
  534. }
  535. }
  536. return transitions;
  537. },
  538. stateMachines: {}
  539. };
  540. //
  541. // Definition of state machines
  542. //
  543. mhchemParser.stateMachines = {
  544. //
  545. // \ce state machines
  546. //
  547. //#region ce
  548. 'ce': { // main parser
  549. transitions: mhchemParser.createTransitions({
  550. 'empty': {
  551. '*': { action_: 'output' } },
  552. 'else': {
  553. '0|1|2': { action_: 'beginsWithBond=false', revisit: true, toContinue: true } },
  554. 'oxidation$': {
  555. '0': { action_: 'oxidation-output' } },
  556. 'CMT': {
  557. 'r': { action_: 'rdt=', nextState: 'rt' },
  558. 'rd': { action_: 'rqt=', nextState: 'rdt' } },
  559. 'arrowUpDown': {
  560. '0|1|2|as': { action_: [ 'sb=false', 'output', 'operator' ], nextState: '1' } },
  561. 'uprightEntities': {
  562. '0|1|2': { action_: [ 'o=', 'output' ], nextState: '1' } },
  563. 'orbital': {
  564. '0|1|2|3': { action_: 'o=', nextState: 'o' } },
  565. '->': {
  566. '0|1|2|3': { action_: 'r=', nextState: 'r' },
  567. 'a|as': { action_: [ 'output', 'r=' ], nextState: 'r' },
  568. '*': { action_: [ 'output', 'r=' ], nextState: 'r' } },
  569. '+': {
  570. 'o': { action_: 'd= kv', nextState: 'd' },
  571. 'd|D': { action_: 'd=', nextState: 'd' },
  572. 'q': { action_: 'd=', nextState: 'qd' },
  573. 'qd|qD': { action_: 'd=', nextState: 'qd' },
  574. 'dq': { action_: [ 'output', 'd=' ], nextState: 'd' },
  575. '3': { action_: [ 'sb=false', 'output', 'operator' ], nextState: '0' } },
  576. 'amount': {
  577. '0|2': { action_: 'a=', nextState: 'a' } },
  578. 'pm-operator': {
  579. '0|1|2|a|as': { action_: [ 'sb=false', 'output', { type_: 'operator', option: '\\pm' } ], nextState: '0' } },
  580. 'operator': {
  581. '0|1|2|a|as': { action_: [ 'sb=false', 'output', 'operator' ], nextState: '0' } },
  582. '-$': {
  583. 'o|q': { action_: [ 'charge or bond', 'output' ], nextState: 'qd' },
  584. 'd': { action_: 'd=', nextState: 'd' },
  585. 'D': { action_: [ 'output', { type_: 'bond', option: "-" } ], nextState: '3' },
  586. 'q': { action_: 'd=', nextState: 'qd' },
  587. 'qd': { action_: 'd=', nextState: 'qd' },
  588. 'qD|dq': { action_: [ 'output', { type_: 'bond', option: "-" } ], nextState: '3' } },
  589. '-9': {
  590. '3|o': { action_: [ 'output', { type_: 'insert', option: 'hyphen' } ], nextState: '3' } },
  591. '- orbital overlap': {
  592. 'o': { action_: [ 'output', { type_: 'insert', option: 'hyphen' } ], nextState: '2' },
  593. 'd': { action_: [ 'output', { type_: 'insert', option: 'hyphen' } ], nextState: '2' } },
  594. '-': {
  595. '0|1|2': { action_: [ { type_: 'output', option: 1 }, 'beginsWithBond=true', { type_: 'bond', option: "-" } ], nextState: '3' },
  596. '3': { action_: { type_: 'bond', option: "-" } },
  597. 'a': { action_: [ 'output', { type_: 'insert', option: 'hyphen' } ], nextState: '2' },
  598. 'as': { action_: [ { type_: 'output', option: 2 }, { type_: 'bond', option: "-" } ], nextState: '3' },
  599. 'b': { action_: 'b=' },
  600. 'o': { action_: { type_: '- after o/d', option: false }, nextState: '2' },
  601. 'q': { action_: { type_: '- after o/d', option: false }, nextState: '2' },
  602. 'd|qd|dq': { action_: { type_: '- after o/d', option: true }, nextState: '2' },
  603. 'D|qD|p': { action_: [ 'output', { type_: 'bond', option: "-" } ], nextState: '3' } },
  604. 'amount2': {
  605. '1|3': { action_: 'a=', nextState: 'a' } },
  606. 'letters': {
  607. '0|1|2|3|a|as|b|p|bp|o': { action_: 'o=', nextState: 'o' },
  608. 'q|dq': { action_: ['output', 'o='], nextState: 'o' },
  609. 'd|D|qd|qD': { action_: 'o after d', nextState: 'o' } },
  610. 'digits': {
  611. 'o': { action_: 'q=', nextState: 'q' },
  612. 'd|D': { action_: 'q=', nextState: 'dq' },
  613. 'q': { action_: [ 'output', 'o=' ], nextState: 'o' },
  614. 'a': { action_: 'o=', nextState: 'o' } },
  615. 'space A': {
  616. 'b|p|bp': {} },
  617. 'space': {
  618. 'a': { nextState: 'as' },
  619. '0': { action_: 'sb=false' },
  620. '1|2': { action_: 'sb=true' },
  621. 'r|rt|rd|rdt|rdq': { action_: 'output', nextState: '0' },
  622. '*': { action_: [ 'output', 'sb=true' ], nextState: '1'} },
  623. '1st-level escape': {
  624. '1|2': { action_: [ 'output', { type_: 'insert+p1', option: '1st-level escape' } ] },
  625. '*': { action_: [ 'output', { type_: 'insert+p1', option: '1st-level escape' } ], nextState: '0' } },
  626. '[(...)]': {
  627. 'r|rt': { action_: 'rd=', nextState: 'rd' },
  628. 'rd|rdt': { action_: 'rq=', nextState: 'rdq' } },
  629. '...': {
  630. 'o|d|D|dq|qd|qD': { action_: [ 'output', { type_: 'bond', option: "..." } ], nextState: '3' },
  631. '*': { action_: [ { type_: 'output', option: 1 }, { type_: 'insert', option: 'ellipsis' } ], nextState: '1' } },
  632. '. |* ': {
  633. '*': { action_: [ 'output', { type_: 'insert', option: 'addition compound' } ], nextState: '1' } },
  634. 'state of aggregation $': {
  635. '*': { action_: [ 'output', 'state of aggregation' ], nextState: '1' } },
  636. '{[(': {
  637. 'a|as|o': { action_: [ 'o=', 'output', 'parenthesisLevel++' ], nextState: '2' },
  638. '0|1|2|3': { action_: [ 'o=', 'output', 'parenthesisLevel++' ], nextState: '2' },
  639. '*': { action_: [ 'output', 'o=', 'output', 'parenthesisLevel++' ], nextState: '2' } },
  640. ')]}': {
  641. '0|1|2|3|b|p|bp|o': { action_: [ 'o=', 'parenthesisLevel--' ], nextState: 'o' },
  642. 'a|as|d|D|q|qd|qD|dq': { action_: [ 'output', 'o=', 'parenthesisLevel--' ], nextState: 'o' } },
  643. ', ': {
  644. '*': { action_: [ 'output', 'comma' ], nextState: '0' } },
  645. '^_': { // ^ and _ without a sensible argument
  646. '*': { } },
  647. '^{(...)}|^($...$)': {
  648. '0|1|2|as': { action_: 'b=', nextState: 'b' },
  649. 'p': { action_: 'b=', nextState: 'bp' },
  650. '3|o': { action_: 'd= kv', nextState: 'D' },
  651. 'q': { action_: 'd=', nextState: 'qD' },
  652. 'd|D|qd|qD|dq': { action_: [ 'output', 'd=' ], nextState: 'D' } },
  653. '^a|^\\x{}{}|^\\x{}|^\\x|\'': {
  654. '0|1|2|as': { action_: 'b=', nextState: 'b' },
  655. 'p': { action_: 'b=', nextState: 'bp' },
  656. '3|o': { action_: 'd= kv', nextState: 'd' },
  657. 'q': { action_: 'd=', nextState: 'qd' },
  658. 'd|qd|D|qD': { action_: 'd=' },
  659. 'dq': { action_: [ 'output', 'd=' ], nextState: 'd' } },
  660. '_{(state of aggregation)}$': {
  661. 'd|D|q|qd|qD|dq': { action_: [ 'output', 'q=' ], nextState: 'q' } },
  662. '_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x': {
  663. '0|1|2|as': { action_: 'p=', nextState: 'p' },
  664. 'b': { action_: 'p=', nextState: 'bp' },
  665. '3|o': { action_: 'q=', nextState: 'q' },
  666. 'd|D': { action_: 'q=', nextState: 'dq' },
  667. 'q|qd|qD|dq': { action_: [ 'output', 'q=' ], nextState: 'q' } },
  668. '=<>': {
  669. '0|1|2|3|a|as|o|q|d|D|qd|qD|dq': { action_: [ { type_: 'output', option: 2 }, 'bond' ], nextState: '3' } },
  670. '#': {
  671. '0|1|2|3|a|as|o': { action_: [ { type_: 'output', option: 2 }, { type_: 'bond', option: "#" } ], nextState: '3' } },
  672. '{}': {
  673. '*': { action_: { type_: 'output', option: 1 }, nextState: '1' } },
  674. '{...}': {
  675. '0|1|2|3|a|as|b|p|bp': { action_: 'o=', nextState: 'o' },
  676. 'o|d|D|q|qd|qD|dq': { action_: [ 'output', 'o=' ], nextState: 'o' } },
  677. '$...$': {
  678. 'a': { action_: 'a=' }, // 2$n$
  679. '0|1|2|3|as|b|p|bp|o': { action_: 'o=', nextState: 'o' }, // not 'amount'
  680. 'as|o': { action_: 'o=' },
  681. 'q|d|D|qd|qD|dq': { action_: [ 'output', 'o=' ], nextState: 'o' } },
  682. '\\bond{(...)}': {
  683. '*': { action_: [ { type_: 'output', option: 2 }, 'bond' ], nextState: "3" } },
  684. '\\frac{(...)}': {
  685. '*': { action_: [ { type_: 'output', option: 1 }, 'frac-output' ], nextState: '3' } },
  686. '\\overset{(...)}': {
  687. '*': { action_: [ { type_: 'output', option: 2 }, 'overset-output' ], nextState: '3' } },
  688. '\\underset{(...)}': {
  689. '*': { action_: [ { type_: 'output', option: 2 }, 'underset-output' ], nextState: '3' } },
  690. '\\underbrace{(...)}': {
  691. '*': { action_: [ { type_: 'output', option: 2 }, 'underbrace-output' ], nextState: '3' } },
  692. '\\color{(...)}{(...)}1|\\color(...){(...)}2': {
  693. '*': { action_: [ { type_: 'output', option: 2 }, 'color-output' ], nextState: '3' } },
  694. '\\color{(...)}0': {
  695. '*': { action_: [ { type_: 'output', option: 2 }, 'color0-output' ] } },
  696. '\\ce{(...)}': {
  697. '*': { action_: [ { type_: 'output', option: 2 }, 'ce' ], nextState: '3' } },
  698. '\\,': {
  699. '*': { action_: [ { type_: 'output', option: 1 }, 'copy' ], nextState: '1' } },
  700. '\\x{}{}|\\x{}|\\x': {
  701. '0|1|2|3|a|as|b|p|bp|o|c0': { action_: [ 'o=', 'output' ], nextState: '3' },
  702. '*': { action_: ['output', 'o=', 'output' ], nextState: '3' } },
  703. 'others': {
  704. '*': { action_: [ { type_: 'output', option: 1 }, 'copy' ], nextState: '3' } },
  705. 'else2': {
  706. 'a': { action_: 'a to o', nextState: 'o', revisit: true },
  707. 'as': { action_: [ 'output', 'sb=true' ], nextState: '1', revisit: true },
  708. 'r|rt|rd|rdt|rdq': { action_: [ 'output' ], nextState: '0', revisit: true },
  709. '*': { action_: [ 'output', 'copy' ], nextState: '3' } }
  710. }),
  711. actions: {
  712. 'o after d': function (buffer, m) {
  713. var ret;
  714. if ((buffer.d || "").match(/^[0-9]+$/)) {
  715. var tmp = buffer.d;
  716. buffer.d = undefined;
  717. ret = this['output'](buffer);
  718. buffer.b = tmp;
  719. } else {
  720. ret = this['output'](buffer);
  721. }
  722. mhchemParser.actions['o='](buffer, m);
  723. return ret;
  724. },
  725. 'd= kv': function (buffer, m) {
  726. buffer.d = m;
  727. buffer.dType = 'kv';
  728. },
  729. 'charge or bond': function (buffer, m) {
  730. if (buffer['beginsWithBond']) {
  731. /** @type {ParserOutput[]} */
  732. var ret = [];
  733. mhchemParser.concatArray(ret, this['output'](buffer));
  734. mhchemParser.concatArray(ret, mhchemParser.actions['bond'](buffer, m, "-"));
  735. return ret;
  736. } else {
  737. buffer.d = m;
  738. }
  739. },
  740. '- after o/d': function (buffer, m, isAfterD) {
  741. var c1 = mhchemParser.patterns.match_('orbital', buffer.o || "");
  742. var c2 = mhchemParser.patterns.match_('one lowercase greek letter $', buffer.o || "");
  743. var c3 = mhchemParser.patterns.match_('one lowercase latin letter $', buffer.o || "");
  744. var c4 = mhchemParser.patterns.match_('$one lowercase latin letter$ $', buffer.o || "");
  745. var hyphenFollows = m==="-" && ( c1 && c1.remainder==="" || c2 || c3 || c4 );
  746. if (hyphenFollows && !buffer.a && !buffer.b && !buffer.p && !buffer.d && !buffer.q && !c1 && c3) {
  747. buffer.o = '$' + buffer.o + '$';
  748. }
  749. /** @type {ParserOutput[]} */
  750. var ret = [];
  751. if (hyphenFollows) {
  752. mhchemParser.concatArray(ret, this['output'](buffer));
  753. ret.push({ type_: 'hyphen' });
  754. } else {
  755. c1 = mhchemParser.patterns.match_('digits', buffer.d || "");
  756. if (isAfterD && c1 && c1.remainder==='') {
  757. mhchemParser.concatArray(ret, mhchemParser.actions['d='](buffer, m));
  758. mhchemParser.concatArray(ret, this['output'](buffer));
  759. } else {
  760. mhchemParser.concatArray(ret, this['output'](buffer));
  761. mhchemParser.concatArray(ret, mhchemParser.actions['bond'](buffer, m, "-"));
  762. }
  763. }
  764. return ret;
  765. },
  766. 'a to o': function (buffer) {
  767. buffer.o = buffer.a;
  768. buffer.a = undefined;
  769. },
  770. 'sb=true': function (buffer) { buffer.sb = true; },
  771. 'sb=false': function (buffer) { buffer.sb = false; },
  772. 'beginsWithBond=true': function (buffer) { buffer['beginsWithBond'] = true; },
  773. 'beginsWithBond=false': function (buffer) { buffer['beginsWithBond'] = false; },
  774. 'parenthesisLevel++': function (buffer) { buffer['parenthesisLevel']++; },
  775. 'parenthesisLevel--': function (buffer) { buffer['parenthesisLevel']--; },
  776. 'state of aggregation': function (buffer, m) {
  777. return { type_: 'state of aggregation', p1: mhchemParser.go(m, 'o') };
  778. },
  779. 'comma': function (buffer, m) {
  780. var a = m.replace(/\s*$/, '');
  781. var withSpace = (a !== m);
  782. if (withSpace && buffer['parenthesisLevel'] === 0) {
  783. return { type_: 'comma enumeration L', p1: a };
  784. } else {
  785. return { type_: 'comma enumeration M', p1: a };
  786. }
  787. },
  788. 'output': function (buffer, m, entityFollows) {
  789. // entityFollows:
  790. // undefined = if we have nothing else to output, also ignore the just read space (buffer.sb)
  791. // 1 = an entity follows, never omit the space if there was one just read before (can only apply to state 1)
  792. // 2 = 1 + the entity can have an amount, so output a\, instead of converting it to o (can only apply to states a|as)
  793. /** @type {ParserOutput | ParserOutput[]} */
  794. var ret;
  795. if (!buffer.r) {
  796. ret = [];
  797. if (!buffer.a && !buffer.b && !buffer.p && !buffer.o && !buffer.q && !buffer.d && !entityFollows) {
  798. //ret = [];
  799. } else {
  800. if (buffer.sb) {
  801. ret.push({ type_: 'entitySkip' });
  802. }
  803. if (!buffer.o && !buffer.q && !buffer.d && !buffer.b && !buffer.p && entityFollows!==2) {
  804. buffer.o = buffer.a;
  805. buffer.a = undefined;
  806. } else if (!buffer.o && !buffer.q && !buffer.d && (buffer.b || buffer.p)) {
  807. buffer.o = buffer.a;
  808. buffer.d = buffer.b;
  809. buffer.q = buffer.p;
  810. buffer.a = buffer.b = buffer.p = undefined;
  811. } else {
  812. if (buffer.o && buffer.dType==='kv' && mhchemParser.patterns.match_('d-oxidation$', buffer.d || "")) {
  813. buffer.dType = 'oxidation';
  814. } else if (buffer.o && buffer.dType==='kv' && !buffer.q) {
  815. buffer.dType = undefined;
  816. }
  817. }
  818. ret.push({
  819. type_: 'chemfive',
  820. a: mhchemParser.go(buffer.a, 'a'),
  821. b: mhchemParser.go(buffer.b, 'bd'),
  822. p: mhchemParser.go(buffer.p, 'pq'),
  823. o: mhchemParser.go(buffer.o, 'o'),
  824. q: mhchemParser.go(buffer.q, 'pq'),
  825. d: mhchemParser.go(buffer.d, (buffer.dType === 'oxidation' ? 'oxidation' : 'bd')),
  826. dType: buffer.dType
  827. });
  828. }
  829. } else { // r
  830. /** @type {ParserOutput[]} */
  831. var rd;
  832. if (buffer.rdt === 'M') {
  833. rd = mhchemParser.go(buffer.rd, 'tex-math');
  834. } else if (buffer.rdt === 'T') {
  835. rd = [ { type_: 'text', p1: buffer.rd || "" } ];
  836. } else {
  837. rd = mhchemParser.go(buffer.rd);
  838. }
  839. /** @type {ParserOutput[]} */
  840. var rq;
  841. if (buffer.rqt === 'M') {
  842. rq = mhchemParser.go(buffer.rq, 'tex-math');
  843. } else if (buffer.rqt === 'T') {
  844. rq = [ { type_: 'text', p1: buffer.rq || ""} ];
  845. } else {
  846. rq = mhchemParser.go(buffer.rq);
  847. }
  848. ret = {
  849. type_: 'arrow',
  850. r: buffer.r,
  851. rd: rd,
  852. rq: rq
  853. };
  854. }
  855. for (var p in buffer) {
  856. if (p !== 'parenthesisLevel' && p !== 'beginsWithBond') {
  857. delete buffer[p];
  858. }
  859. }
  860. return ret;
  861. },
  862. 'oxidation-output': function (buffer, m) {
  863. var ret = [ "{" ];
  864. mhchemParser.concatArray(ret, mhchemParser.go(m, 'oxidation'));
  865. ret.push("}");
  866. return ret;
  867. },
  868. 'frac-output': function (buffer, m) {
  869. return { type_: 'frac-ce', p1: mhchemParser.go(m[0]), p2: mhchemParser.go(m[1]) };
  870. },
  871. 'overset-output': function (buffer, m) {
  872. return { type_: 'overset', p1: mhchemParser.go(m[0]), p2: mhchemParser.go(m[1]) };
  873. },
  874. 'underset-output': function (buffer, m) {
  875. return { type_: 'underset', p1: mhchemParser.go(m[0]), p2: mhchemParser.go(m[1]) };
  876. },
  877. 'underbrace-output': function (buffer, m) {
  878. return { type_: 'underbrace', p1: mhchemParser.go(m[0]), p2: mhchemParser.go(m[1]) };
  879. },
  880. 'color-output': function (buffer, m) {
  881. return { type_: 'color', color1: m[0], color2: mhchemParser.go(m[1]) };
  882. },
  883. 'r=': function (buffer, m) { buffer.r = m; },
  884. 'rdt=': function (buffer, m) { buffer.rdt = m; },
  885. 'rd=': function (buffer, m) { buffer.rd = m; },
  886. 'rqt=': function (buffer, m) { buffer.rqt = m; },
  887. 'rq=': function (buffer, m) { buffer.rq = m; },
  888. 'operator': function (buffer, m, p1) { return { type_: 'operator', kind_: (p1 || m) }; }
  889. }
  890. },
  891. 'a': {
  892. transitions: mhchemParser.createTransitions({
  893. 'empty': {
  894. '*': {} },
  895. '1/2$': {
  896. '0': { action_: '1/2' } },
  897. 'else': {
  898. '0': { nextState: '1', revisit: true } },
  899. '$(...)$': {
  900. '*': { action_: 'tex-math tight', nextState: '1' } },
  901. ',': {
  902. '*': { action_: { type_: 'insert', option: 'commaDecimal' } } },
  903. 'else2': {
  904. '*': { action_: 'copy' } }
  905. }),
  906. actions: {}
  907. },
  908. 'o': {
  909. transitions: mhchemParser.createTransitions({
  910. 'empty': {
  911. '*': {} },
  912. '1/2$': {
  913. '0': { action_: '1/2' } },
  914. 'else': {
  915. '0': { nextState: '1', revisit: true } },
  916. 'letters': {
  917. '*': { action_: 'rm' } },
  918. '\\ca': {
  919. '*': { action_: { type_: 'insert', option: 'circa' } } },
  920. '\\x{}{}|\\x{}|\\x': {
  921. '*': { action_: 'copy' } },
  922. '${(...)}$|$(...)$': {
  923. '*': { action_: 'tex-math' } },
  924. '{(...)}': {
  925. '*': { action_: '{text}' } },
  926. 'else2': {
  927. '*': { action_: 'copy' } }
  928. }),
  929. actions: {}
  930. },
  931. 'text': {
  932. transitions: mhchemParser.createTransitions({
  933. 'empty': {
  934. '*': { action_: 'output' } },
  935. '{...}': {
  936. '*': { action_: 'text=' } },
  937. '${(...)}$|$(...)$': {
  938. '*': { action_: 'tex-math' } },
  939. '\\greek': {
  940. '*': { action_: [ 'output', 'rm' ] } },
  941. '\\,|\\x{}{}|\\x{}|\\x': {
  942. '*': { action_: [ 'output', 'copy' ] } },
  943. 'else': {
  944. '*': { action_: 'text=' } }
  945. }),
  946. actions: {
  947. 'output': function (buffer) {
  948. if (buffer.text_) {
  949. /** @type {ParserOutput} */
  950. var ret = { type_: 'text', p1: buffer.text_ };
  951. for (var p in buffer) { delete buffer[p]; }
  952. return ret;
  953. }
  954. }
  955. }
  956. },
  957. 'pq': {
  958. transitions: mhchemParser.createTransitions({
  959. 'empty': {
  960. '*': {} },
  961. 'state of aggregation $': {
  962. '*': { action_: 'state of aggregation' } },
  963. 'i$': {
  964. '0': { nextState: '!f', revisit: true } },
  965. '(KV letters),': {
  966. '0': { action_: 'rm', nextState: '0' } },
  967. 'formula$': {
  968. '0': { nextState: 'f', revisit: true } },
  969. '1/2$': {
  970. '0': { action_: '1/2' } },
  971. 'else': {
  972. '0': { nextState: '!f', revisit: true } },
  973. '${(...)}$|$(...)$': {
  974. '*': { action_: 'tex-math' } },
  975. '{(...)}': {
  976. '*': { action_: 'text' } },
  977. 'a-z': {
  978. 'f': { action_: 'tex-math' } },
  979. 'letters': {
  980. '*': { action_: 'rm' } },
  981. '-9.,9': {
  982. '*': { action_: '9,9' } },
  983. ',': {
  984. '*': { action_: { type_: 'insert+p1', option: 'comma enumeration S' } } },
  985. '\\color{(...)}{(...)}1|\\color(...){(...)}2': {
  986. '*': { action_: 'color-output' } },
  987. '\\color{(...)}0': {
  988. '*': { action_: 'color0-output' } },
  989. '\\ce{(...)}': {
  990. '*': { action_: 'ce' } },
  991. '\\,|\\x{}{}|\\x{}|\\x': {
  992. '*': { action_: 'copy' } },
  993. 'else2': {
  994. '*': { action_: 'copy' } }
  995. }),
  996. actions: {
  997. 'state of aggregation': function (buffer, m) {
  998. return { type_: 'state of aggregation subscript', p1: mhchemParser.go(m, 'o') };
  999. },
  1000. 'color-output': function (buffer, m) {
  1001. return { type_: 'color', color1: m[0], color2: mhchemParser.go(m[1], 'pq') };
  1002. }
  1003. }
  1004. },
  1005. 'bd': {
  1006. transitions: mhchemParser.createTransitions({
  1007. 'empty': {
  1008. '*': {} },
  1009. 'x$': {
  1010. '0': { nextState: '!f', revisit: true } },
  1011. 'formula$': {
  1012. '0': { nextState: 'f', revisit: true } },
  1013. 'else': {
  1014. '0': { nextState: '!f', revisit: true } },
  1015. '-9.,9 no missing 0': {
  1016. '*': { action_: '9,9' } },
  1017. '.': {
  1018. '*': { action_: { type_: 'insert', option: 'electron dot' } } },
  1019. 'a-z': {
  1020. 'f': { action_: 'tex-math' } },
  1021. 'x': {
  1022. '*': { action_: { type_: 'insert', option: 'KV x' } } },
  1023. 'letters': {
  1024. '*': { action_: 'rm' } },
  1025. '\'': {
  1026. '*': { action_: { type_: 'insert', option: 'prime' } } },
  1027. '${(...)}$|$(...)$': {
  1028. '*': { action_: 'tex-math' } },
  1029. '{(...)}': {
  1030. '*': { action_: 'text' } },
  1031. '\\color{(...)}{(...)}1|\\color(...){(...)}2': {
  1032. '*': { action_: 'color-output' } },
  1033. '\\color{(...)}0': {
  1034. '*': { action_: 'color0-output' } },
  1035. '\\ce{(...)}': {
  1036. '*': { action_: 'ce' } },
  1037. '\\,|\\x{}{}|\\x{}|\\x': {
  1038. '*': { action_: 'copy' } },
  1039. 'else2': {
  1040. '*': { action_: 'copy' } }
  1041. }),
  1042. actions: {
  1043. 'color-output': function (buffer, m) {
  1044. return { type_: 'color', color1: m[0], color2: mhchemParser.go(m[1], 'bd') };
  1045. }
  1046. }
  1047. },
  1048. 'oxidation': {
  1049. transitions: mhchemParser.createTransitions({
  1050. 'empty': {
  1051. '*': {} },
  1052. 'roman numeral': {
  1053. '*': { action_: 'roman-numeral' } },
  1054. '${(...)}$|$(...)$': {
  1055. '*': { action_: 'tex-math' } },
  1056. 'else': {
  1057. '*': { action_: 'copy' } }
  1058. }),
  1059. actions: {
  1060. 'roman-numeral': function (buffer, m) { return { type_: 'roman numeral', p1: m || "" }; }
  1061. }
  1062. },
  1063. 'tex-math': {
  1064. transitions: mhchemParser.createTransitions({
  1065. 'empty': {
  1066. '*': { action_: 'output' } },
  1067. '\\ce{(...)}': {
  1068. '*': { action_: [ 'output', 'ce' ] } },
  1069. '{...}|\\,|\\x{}{}|\\x{}|\\x': {
  1070. '*': { action_: 'o=' } },
  1071. 'else': {
  1072. '*': { action_: 'o=' } }
  1073. }),
  1074. actions: {
  1075. 'output': function (buffer) {
  1076. if (buffer.o) {
  1077. /** @type {ParserOutput} */
  1078. var ret = { type_: 'tex-math', p1: buffer.o };
  1079. for (var p in buffer) { delete buffer[p]; }
  1080. return ret;
  1081. }
  1082. }
  1083. }
  1084. },
  1085. 'tex-math tight': {
  1086. transitions: mhchemParser.createTransitions({
  1087. 'empty': {
  1088. '*': { action_: 'output' } },
  1089. '\\ce{(...)}': {
  1090. '*': { action_: [ 'output', 'ce' ] } },
  1091. '{...}|\\,|\\x{}{}|\\x{}|\\x': {
  1092. '*': { action_: 'o=' } },
  1093. '-|+': {
  1094. '*': { action_: 'tight operator' } },
  1095. 'else': {
  1096. '*': { action_: 'o=' } }
  1097. }),
  1098. actions: {
  1099. 'tight operator': function (buffer, m) { buffer.o = (buffer.o || "") + "{"+m+"}"; },
  1100. 'output': function (buffer) {
  1101. if (buffer.o) {
  1102. /** @type {ParserOutput} */
  1103. var ret = { type_: 'tex-math', p1: buffer.o };
  1104. for (var p in buffer) { delete buffer[p]; }
  1105. return ret;
  1106. }
  1107. }
  1108. }
  1109. },
  1110. '9,9': {
  1111. transitions: mhchemParser.createTransitions({
  1112. 'empty': {
  1113. '*': {} },
  1114. ',': {
  1115. '*': { action_: 'comma' } },
  1116. 'else': {
  1117. '*': { action_: 'copy' } }
  1118. }),
  1119. actions: {
  1120. 'comma': function () { return { type_: 'commaDecimal' }; }
  1121. }
  1122. },
  1123. //#endregion
  1124. //
  1125. // \pu state machines
  1126. //
  1127. //#region pu
  1128. 'pu': {
  1129. transitions: mhchemParser.createTransitions({
  1130. 'empty': {
  1131. '*': { action_: 'output' } },
  1132. 'space$': {
  1133. '*': { action_: [ 'output', 'space' ] } },
  1134. '{[(|)]}': {
  1135. '0|a': { action_: 'copy' } },
  1136. '(-)(9)^(-9)': {
  1137. '0': { action_: 'number^', nextState: 'a' } },
  1138. '(-)(9.,9)(e)(99)': {
  1139. '0': { action_: 'enumber', nextState: 'a' } },
  1140. 'space': {
  1141. '0|a': {} },
  1142. 'pm-operator': {
  1143. '0|a': { action_: { type_: 'operator', option: '\\pm' }, nextState: '0' } },
  1144. 'operator': {
  1145. '0|a': { action_: 'copy', nextState: '0' } },
  1146. '//': {
  1147. 'd': { action_: 'o=', nextState: '/' } },
  1148. '/': {
  1149. 'd': { action_: 'o=', nextState: '/' } },
  1150. '{...}|else': {
  1151. '0|d': { action_: 'd=', nextState: 'd' },
  1152. 'a': { action_: [ 'space', 'd=' ], nextState: 'd' },
  1153. '/|q': { action_: 'q=', nextState: 'q' } }
  1154. }),
  1155. actions: {
  1156. 'enumber': function (buffer, m) {
  1157. /** @type {ParserOutput[]} */
  1158. var ret = [];
  1159. if (m[0] === "+-" || m[0] === "+/-") {
  1160. ret.push("\\pm ");
  1161. } else if (m[0]) {
  1162. ret.push(m[0]);
  1163. }
  1164. if (m[1]) {
  1165. mhchemParser.concatArray(ret, mhchemParser.go(m[1], 'pu-9,9'));
  1166. if (m[2]) {
  1167. if (m[2].match(/[,.]/)) {
  1168. mhchemParser.concatArray(ret, mhchemParser.go(m[2], 'pu-9,9'));
  1169. } else {
  1170. ret.push(m[2]);
  1171. }
  1172. }
  1173. m[3] = m[4] || m[3];
  1174. if (m[3]) {
  1175. m[3] = m[3].trim();
  1176. if (m[3] === "e" || m[3].substr(0, 1) === "*") {
  1177. ret.push({ type_: 'cdot' });
  1178. } else {
  1179. ret.push({ type_: 'times' });
  1180. }
  1181. }
  1182. }
  1183. if (m[3]) {
  1184. ret.push("10^{"+m[5]+"}");
  1185. }
  1186. return ret;
  1187. },
  1188. 'number^': function (buffer, m) {
  1189. /** @type {ParserOutput[]} */
  1190. var ret = [];
  1191. if (m[0] === "+-" || m[0] === "+/-") {
  1192. ret.push("\\pm ");
  1193. } else if (m[0]) {
  1194. ret.push(m[0]);
  1195. }
  1196. mhchemParser.concatArray(ret, mhchemParser.go(m[1], 'pu-9,9'));
  1197. ret.push("^{"+m[2]+"}");
  1198. return ret;
  1199. },
  1200. 'operator': function (buffer, m, p1) { return { type_: 'operator', kind_: (p1 || m) }; },
  1201. 'space': function () { return { type_: 'pu-space-1' }; },
  1202. 'output': function (buffer) {
  1203. /** @type {ParserOutput | ParserOutput[]} */
  1204. var ret;
  1205. var md = mhchemParser.patterns.match_('{(...)}', buffer.d || "");
  1206. if (md && md.remainder === '') { buffer.d = md.match_; }
  1207. var mq = mhchemParser.patterns.match_('{(...)}', buffer.q || "");
  1208. if (mq && mq.remainder === '') { buffer.q = mq.match_; }
  1209. if (buffer.d) {
  1210. buffer.d = buffer.d.replace(/\u00B0C|\^oC|\^{o}C/g, "{}^{\\circ}C");
  1211. buffer.d = buffer.d.replace(/\u00B0F|\^oF|\^{o}F/g, "{}^{\\circ}F");
  1212. }
  1213. if (buffer.q) { // fraction
  1214. buffer.q = buffer.q.replace(/\u00B0C|\^oC|\^{o}C/g, "{}^{\\circ}C");
  1215. buffer.q = buffer.q.replace(/\u00B0F|\^oF|\^{o}F/g, "{}^{\\circ}F");
  1216. var b5 = {
  1217. d: mhchemParser.go(buffer.d, 'pu'),
  1218. q: mhchemParser.go(buffer.q, 'pu')
  1219. };
  1220. if (buffer.o === '//') {
  1221. ret = { type_: 'pu-frac', p1: b5.d, p2: b5.q };
  1222. } else {
  1223. ret = b5.d;
  1224. if (b5.d.length > 1 || b5.q.length > 1) {
  1225. ret.push({ type_: ' / ' });
  1226. } else {
  1227. ret.push({ type_: '/' });
  1228. }
  1229. mhchemParser.concatArray(ret, b5.q);
  1230. }
  1231. } else { // no fraction
  1232. ret = mhchemParser.go(buffer.d, 'pu-2');
  1233. }
  1234. for (var p in buffer) { delete buffer[p]; }
  1235. return ret;
  1236. }
  1237. }
  1238. },
  1239. 'pu-2': {
  1240. transitions: mhchemParser.createTransitions({
  1241. 'empty': {
  1242. '*': { action_: 'output' } },
  1243. '*': {
  1244. '*': { action_: [ 'output', 'cdot' ], nextState: '0' } },
  1245. '\\x': {
  1246. '*': { action_: 'rm=' } },
  1247. 'space': {
  1248. '*': { action_: [ 'output', 'space' ], nextState: '0' } },
  1249. '^{(...)}|^(-1)': {
  1250. '1': { action_: '^(-1)' } },
  1251. '-9.,9': {
  1252. '0': { action_: 'rm=', nextState: '0' },
  1253. '1': { action_: '^(-1)', nextState: '0' } },
  1254. '{...}|else': {
  1255. '*': { action_: 'rm=', nextState: '1' } }
  1256. }),
  1257. actions: {
  1258. 'cdot': function () { return { type_: 'tight cdot' }; },
  1259. '^(-1)': function (buffer, m) { buffer.rm += "^{"+m+"}"; },
  1260. 'space': function () { return { type_: 'pu-space-2' }; },
  1261. 'output': function (buffer) {
  1262. /** @type {ParserOutput | ParserOutput[]} */
  1263. var ret = [];
  1264. if (buffer.rm) {
  1265. var mrm = mhchemParser.patterns.match_('{(...)}', buffer.rm || "");
  1266. if (mrm && mrm.remainder === '') {
  1267. ret = mhchemParser.go(mrm.match_, 'pu');
  1268. } else {
  1269. ret = { type_: 'rm', p1: buffer.rm };
  1270. }
  1271. }
  1272. for (var p in buffer) { delete buffer[p]; }
  1273. return ret;
  1274. }
  1275. }
  1276. },
  1277. 'pu-9,9': {
  1278. transitions: mhchemParser.createTransitions({
  1279. 'empty': {
  1280. '0': { action_: 'output-0' },
  1281. 'o': { action_: 'output-o' } },
  1282. ',': {
  1283. '0': { action_: [ 'output-0', 'comma' ], nextState: 'o' } },
  1284. '.': {
  1285. '0': { action_: [ 'output-0', 'copy' ], nextState: 'o' } },
  1286. 'else': {
  1287. '*': { action_: 'text=' } }
  1288. }),
  1289. actions: {
  1290. 'comma': function () { return { type_: 'commaDecimal' }; },
  1291. 'output-0': function (buffer) {
  1292. /** @type {ParserOutput[]} */
  1293. var ret = [];
  1294. buffer.text_ = buffer.text_ || "";
  1295. if (buffer.text_.length > 4) {
  1296. var a = buffer.text_.length % 3;
  1297. if (a === 0) { a = 3; }
  1298. for (var i=buffer.text_.length-3; i>0; i-=3) {
  1299. ret.push(buffer.text_.substr(i, 3));
  1300. ret.push({ type_: '1000 separator' });
  1301. }
  1302. ret.push(buffer.text_.substr(0, a));
  1303. ret.reverse();
  1304. } else {
  1305. ret.push(buffer.text_);
  1306. }
  1307. for (var p in buffer) { delete buffer[p]; }
  1308. return ret;
  1309. },
  1310. 'output-o': function (buffer) {
  1311. /** @type {ParserOutput[]} */
  1312. var ret = [];
  1313. buffer.text_ = buffer.text_ || "";
  1314. if (buffer.text_.length > 4) {
  1315. var a = buffer.text_.length - 3;
  1316. for (var i=0; i<a; i+=3) {
  1317. ret.push(buffer.text_.substr(i, 3));
  1318. ret.push({ type_: '1000 separator' });
  1319. }
  1320. ret.push(buffer.text_.substr(i));
  1321. } else {
  1322. ret.push(buffer.text_);
  1323. }
  1324. for (var p in buffer) { delete buffer[p]; }
  1325. return ret;
  1326. }
  1327. }
  1328. }
  1329. //#endregion
  1330. };
  1331. //
  1332. // texify: Take MhchemParser output and convert it to TeX
  1333. //
  1334. /** @type {Texify} */
  1335. var texify = {
  1336. go: function (input, isInner) { // (recursive, max 4 levels)
  1337. if (!input) { return ""; }
  1338. var res = "";
  1339. var cee = false;
  1340. for (var i=0; i < input.length; i++) {
  1341. var inputi = input[i];
  1342. if (typeof inputi === "string") {
  1343. res += inputi;
  1344. } else {
  1345. res += texify._go2(inputi);
  1346. if (inputi.type_ === '1st-level escape') { cee = true; }
  1347. }
  1348. }
  1349. if (!isInner && !cee && res) {
  1350. res = "{" + res + "}";
  1351. }
  1352. return res;
  1353. },
  1354. _goInner: function (input) {
  1355. if (!input) { return input; }
  1356. return texify.go(input, true);
  1357. },
  1358. _go2: function (buf) {
  1359. /** @type {undefined | string} */
  1360. var res;
  1361. switch (buf.type_) {
  1362. case 'chemfive':
  1363. res = "";
  1364. var b5 = {
  1365. a: texify._goInner(buf.a),
  1366. b: texify._goInner(buf.b),
  1367. p: texify._goInner(buf.p),
  1368. o: texify._goInner(buf.o),
  1369. q: texify._goInner(buf.q),
  1370. d: texify._goInner(buf.d)
  1371. };
  1372. //
  1373. // a
  1374. //
  1375. if (b5.a) {
  1376. if (b5.a.match(/^[+\-]/)) { b5.a = "{"+b5.a+"}"; }
  1377. res += b5.a + "\\,";
  1378. }
  1379. //
  1380. // b and p
  1381. //
  1382. if (b5.b || b5.p) {
  1383. res += "{\\vphantom{X}}";
  1384. res += "^{\\hphantom{"+(b5.b||"")+"}}_{\\hphantom{"+(b5.p||"")+"}}";
  1385. res += "{\\vphantom{X}}";
  1386. res += "^{\\smash[t]{\\vphantom{2}}\\llap{"+(b5.b||"")+"}}";
  1387. res += "_{\\vphantom{2}\\llap{\\smash[t]{"+(b5.p||"")+"}}}";
  1388. }
  1389. //
  1390. // o
  1391. //
  1392. if (b5.o) {
  1393. if (b5.o.match(/^[+\-]/)) { b5.o = "{"+b5.o+"}"; }
  1394. res += b5.o;
  1395. }
  1396. //
  1397. // q and d
  1398. //
  1399. if (buf.dType === 'kv') {
  1400. if (b5.d || b5.q) {
  1401. res += "{\\vphantom{X}}";
  1402. }
  1403. if (b5.d) {
  1404. res += "^{"+b5.d+"}";
  1405. }
  1406. if (b5.q) {
  1407. res += "_{\\smash[t]{"+b5.q+"}}";
  1408. }
  1409. } else if (buf.dType === 'oxidation') {
  1410. if (b5.d) {
  1411. res += "{\\vphantom{X}}";
  1412. res += "^{"+b5.d+"}";
  1413. }
  1414. if (b5.q) {
  1415. res += "{\\vphantom{X}}";
  1416. res += "_{\\smash[t]{"+b5.q+"}}";
  1417. }
  1418. } else {
  1419. if (b5.q) {
  1420. res += "{\\vphantom{X}}";
  1421. res += "_{\\smash[t]{"+b5.q+"}}";
  1422. }
  1423. if (b5.d) {
  1424. res += "{\\vphantom{X}}";
  1425. res += "^{"+b5.d+"}";
  1426. }
  1427. }
  1428. break;
  1429. case 'rm':
  1430. res = "\\mathrm{"+buf.p1+"}";
  1431. break;
  1432. case 'text':
  1433. if (buf.p1.match(/[\^_]/)) {
  1434. buf.p1 = buf.p1.replace(" ", "~").replace("-", "\\text{-}");
  1435. res = "\\mathrm{"+buf.p1+"}";
  1436. } else {
  1437. res = "\\text{"+buf.p1+"}";
  1438. }
  1439. break;
  1440. case 'roman numeral':
  1441. res = "\\mathrm{"+buf.p1+"}";
  1442. break;
  1443. case 'state of aggregation':
  1444. res = "\\mskip2mu "+texify._goInner(buf.p1);
  1445. break;
  1446. case 'state of aggregation subscript':
  1447. res = "\\mskip1mu "+texify._goInner(buf.p1);
  1448. break;
  1449. case 'bond':
  1450. res = texify._getBond(buf.kind_);
  1451. if (!res) {
  1452. throw ["MhchemErrorBond", "mhchem Error. Unknown bond type (" + buf.kind_ + ")"];
  1453. }
  1454. break;
  1455. case 'frac':
  1456. var c = "\\frac{" + buf.p1 + "}{" + buf.p2 + "}";
  1457. res = "\\mathchoice{\\textstyle"+c+"}{"+c+"}{"+c+"}{"+c+"}";
  1458. break;
  1459. case 'pu-frac':
  1460. var d = "\\frac{" + texify._goInner(buf.p1) + "}{" + texify._goInner(buf.p2) + "}";
  1461. res = "\\mathchoice{\\textstyle"+d+"}{"+d+"}{"+d+"}{"+d+"}";
  1462. break;
  1463. case 'tex-math':
  1464. res = buf.p1 + " ";
  1465. break;
  1466. case 'frac-ce':
  1467. res = "\\frac{" + texify._goInner(buf.p1) + "}{" + texify._goInner(buf.p2) + "}";
  1468. break;
  1469. case 'overset':
  1470. res = "\\overset{" + texify._goInner(buf.p1) + "}{" + texify._goInner(buf.p2) + "}";
  1471. break;
  1472. case 'underset':
  1473. res = "\\underset{" + texify._goInner(buf.p1) + "}{" + texify._goInner(buf.p2) + "}";
  1474. break;
  1475. case 'underbrace':
  1476. res = "\\underbrace{" + texify._goInner(buf.p1) + "}_{" + texify._goInner(buf.p2) + "}";
  1477. break;
  1478. case 'color':
  1479. res = "{\\color{" + buf.color1 + "}{" + texify._goInner(buf.color2) + "}}";
  1480. break;
  1481. case 'color0':
  1482. res = "\\color{" + buf.color + "}";
  1483. break;
  1484. case 'arrow':
  1485. var b6 = {
  1486. rd: texify._goInner(buf.rd),
  1487. rq: texify._goInner(buf.rq)
  1488. };
  1489. var arrow = texify._getArrow(buf.r);
  1490. if (b6.rd || b6.rq) {
  1491. if (buf.r === "<=>" || buf.r === "<=>>" || buf.r === "<<=>" || buf.r === "<-->") {
  1492. // arrows that cannot stretch correctly yet, https://github.com/mathjax/MathJax/issues/1491
  1493. arrow = "\\long"+arrow;
  1494. if (b6.rd) { arrow = "\\overset{"+b6.rd+"}{"+arrow+"}"; }
  1495. if (b6.rq) { arrow = "\\underset{\\lower7mu{"+b6.rq+"}}{"+arrow+"}"; }
  1496. arrow = " {}\\mathrel{"+arrow+"}{} ";
  1497. } else {
  1498. if (b6.rq) { arrow += "[{"+b6.rq+"}]"; }
  1499. arrow += "{"+b6.rd+"}";
  1500. arrow = " {}\\mathrel{\\x"+arrow+"}{} ";
  1501. }
  1502. } else {
  1503. arrow = " {}\\mathrel{\\long"+arrow+"}{} ";
  1504. }
  1505. res = arrow;
  1506. break;
  1507. case 'operator':
  1508. res = texify._getOperator(buf.kind_);
  1509. break;
  1510. case '1st-level escape':
  1511. res = buf.p1+" "; // &, \\\\, \\hlin
  1512. break;
  1513. case 'space':
  1514. res = " ";
  1515. break;
  1516. case 'entitySkip':
  1517. res = "~";
  1518. break;
  1519. case 'pu-space-1':
  1520. res = "~";
  1521. break;
  1522. case 'pu-space-2':
  1523. res = "\\mkern3mu ";
  1524. break;
  1525. case '1000 separator':
  1526. res = "\\mkern2mu ";
  1527. break;
  1528. case 'commaDecimal':
  1529. res = "{,}";
  1530. break;
  1531. case 'comma enumeration L':
  1532. res = "{"+buf.p1+"}\\mkern6mu ";
  1533. break;
  1534. case 'comma enumeration M':
  1535. res = "{"+buf.p1+"}\\mkern3mu ";
  1536. break;
  1537. case 'comma enumeration S':
  1538. res = "{"+buf.p1+"}\\mkern1mu ";
  1539. break;
  1540. case 'hyphen':
  1541. res = "\\text{-}";
  1542. break;
  1543. case 'addition compound':
  1544. res = "\\,{\\cdot}\\,";
  1545. break;
  1546. case 'electron dot':
  1547. res = "\\mkern1mu \\bullet\\mkern1mu ";
  1548. break;
  1549. case 'KV x':
  1550. res = "{\\times}";
  1551. break;
  1552. case 'prime':
  1553. res = "\\prime ";
  1554. break;
  1555. case 'cdot':
  1556. res = "\\cdot ";
  1557. break;
  1558. case 'tight cdot':
  1559. res = "\\mkern1mu{\\cdot}\\mkern1mu ";
  1560. break;
  1561. case 'times':
  1562. res = "\\times ";
  1563. break;
  1564. case 'circa':
  1565. res = "{\\sim}";
  1566. break;
  1567. case '^':
  1568. res = "uparrow";
  1569. break;
  1570. case 'v':
  1571. res = "downarrow";
  1572. break;
  1573. case 'ellipsis':
  1574. res = "\\ldots ";
  1575. break;
  1576. case '/':
  1577. res = "/";
  1578. break;
  1579. case ' / ':
  1580. res = "\\,/\\,";
  1581. break;
  1582. default:
  1583. assertNever(buf);
  1584. throw ["MhchemBugT", "mhchem bug T. Please report."]; // Missing texify rule or unknown MhchemParser output
  1585. }
  1586. assertString(res);
  1587. return res;
  1588. },
  1589. _getArrow: function (a) {
  1590. switch (a) {
  1591. case "->": return "rightarrow";
  1592. case "\u2192": return "rightarrow";
  1593. case "\u27F6": return "rightarrow";
  1594. case "<-": return "leftarrow";
  1595. case "<->": return "leftrightarrow";
  1596. case "<-->": return "leftrightarrows";
  1597. case "<=>": return "rightleftharpoons";
  1598. case "\u21CC": return "rightleftharpoons";
  1599. case "<=>>": return "Rightleftharpoons";
  1600. case "<<=>": return "Leftrightharpoons";
  1601. default:
  1602. assertNever(a);
  1603. throw ["MhchemBugT", "mhchem bug T. Please report."];
  1604. }
  1605. },
  1606. _getBond: function (a) {
  1607. switch (a) {
  1608. case "-": return "{-}";
  1609. case "1": return "{-}";
  1610. case "=": return "{=}";
  1611. case "2": return "{=}";
  1612. case "#": return "{\\equiv}";
  1613. case "3": return "{\\equiv}";
  1614. case "~": return "{\\tripledash}";
  1615. case "~-": return "{\\rlap{\\lower.1em{-}}\\raise.1em{\\tripledash}}";
  1616. case "~=": return "{\\rlap{\\lower.2em{-}}\\rlap{\\raise.2em{\\tripledash}}-}";
  1617. case "~--": return "{\\rlap{\\lower.2em{-}}\\rlap{\\raise.2em{\\tripledash}}-}";
  1618. case "-~-": return "{\\rlap{\\lower.2em{-}}\\rlap{\\raise.2em{-}}\\tripledash}";
  1619. case "...": return "{{\\cdot}{\\cdot}{\\cdot}}";
  1620. case "....": return "{{\\cdot}{\\cdot}{\\cdot}{\\cdot}}";
  1621. case "->": return "{\\rightarrow}";
  1622. case "<-": return "{\\leftarrow}";
  1623. case "<": return "{<}";
  1624. case ">": return "{>}";
  1625. default:
  1626. assertNever(a);
  1627. throw ["MhchemBugT", "mhchem bug T. Please report."];
  1628. }
  1629. },
  1630. _getOperator: function (a) {
  1631. switch (a) {
  1632. case "+": return " {}+{} ";
  1633. case "-": return " {}-{} ";
  1634. case "=": return " {}={} ";
  1635. case "<": return " {}<{} ";
  1636. case ">": return " {}>{} ";
  1637. case "<<": return " {}\\ll{} ";
  1638. case ">>": return " {}\\gg{} ";
  1639. case "\\pm": return " {}\\pm{} ";
  1640. case "\\approx": return " {}\\approx{} ";
  1641. case "$\\approx$": return " {}\\approx{} ";
  1642. case "v": return " \\downarrow{} ";
  1643. case "(v)": return " \\downarrow{} ";
  1644. case "^": return " \\uparrow{} ";
  1645. case "(^)": return " \\uparrow{} ";
  1646. default:
  1647. assertNever(a);
  1648. throw ["MhchemBugT", "mhchem bug T. Please report."];
  1649. }
  1650. }
  1651. };
  1652. //
  1653. // Helpers for code anaylsis
  1654. // Will show type error at calling position
  1655. //
  1656. /** @param {number} a */
  1657. function assertNever(a) {}
  1658. /** @param {string} a */
  1659. function assertString(a) {}
  1660. //
  1661. // MathJax definitions
  1662. //
  1663. MathJax.Extension["TeX/mhchem"].CE = CE;
  1664. /***************************************************************************/
  1665. TEX.Definitions.Add({
  1666. macros: {
  1667. //
  1668. // Set up the macros for chemistry
  1669. //
  1670. ce: "CE",
  1671. pu: "PU",
  1672. //
  1673. // Make these load AMSmath package (redefined below when loaded)
  1674. //
  1675. xleftrightarrow: ["Extension", "AMSmath"],
  1676. xrightleftharpoons: ["Extension", "AMSmath"],
  1677. xRightleftharpoons: ["Extension", "AMSmath"],
  1678. xLeftrightharpoons: ["Extension", "AMSmath"],
  1679. // FIXME: These don't work well in FF NativeMML mode
  1680. longrightleftharpoons: ["Macro", "\\stackrel{\\textstyle{-}\\!\\!{\\rightharpoonup}}{\\smash{{\\leftharpoondown}\\!\\!{-}}}"],
  1681. longRightleftharpoons: ["Macro", "\\stackrel{\\textstyle{-}\\!\\!{\\rightharpoonup}}{\\smash{\\leftharpoondown}}"],
  1682. longLeftrightharpoons: ["Macro", "\\stackrel{\\textstyle\\vphantom{{-}}{\\rightharpoonup}}{\\smash{{\\leftharpoondown}\\!\\!{-}}}"],
  1683. longleftrightarrows: ["Macro", "\\stackrel{\\longrightarrow}{\\smash{\\longleftarrow}\\Rule{0px}{.25em}{0px}}"],
  1684. //
  1685. // Needed for \bond for the ~ forms
  1686. // Not perfectly aligned when zoomed in, but on 100%
  1687. //
  1688. tripledash: ["Macro", "\\vphantom{-}\\raise2mu{\\kern2mu\\tiny\\text{-}\\kern1mu\\text{-}\\kern1mu\\text{-}\\kern2mu}"]
  1689. },
  1690. }, null, true);
  1691. if (!MathJax.Extension["TeX/AMSmath"]) {
  1692. TEX.Definitions.Add({
  1693. macros: {
  1694. xrightarrow: ["Extension", "AMSmath"],
  1695. xleftarrow: ["Extension", "AMSmath"]
  1696. }
  1697. }, null, true);
  1698. }
  1699. //
  1700. // These arrows need to wait until AMSmath is loaded
  1701. //
  1702. MathJax.Hub.Register.StartupHook("TeX AMSmath Ready", function () {
  1703. TEX.Definitions.Add({
  1704. macros: {
  1705. //
  1706. // Some of these are hacks for now
  1707. //
  1708. xleftrightarrow: ["xArrow", 0x2194, 6, 6],
  1709. xrightleftharpoons: ["xArrow", 0x21CC, 5, 7], // FIXME: doesn't stretch in HTML-CSS output
  1710. xRightleftharpoons: ["xArrow", 0x21CC, 5, 7], // FIXME: how should this be handled?
  1711. xLeftrightharpoons: ["xArrow", 0x21CC, 5, 7]
  1712. }
  1713. }, null, true);
  1714. });
  1715. TEX.Parse.Augment({
  1716. //
  1717. // Implements \ce and friends
  1718. //
  1719. CE: function (name) {
  1720. var arg = this.GetArgument(name);
  1721. var tex = CE(arg).Parse();
  1722. this.string = tex + this.string.substr(this.i); this.i = 0;
  1723. },
  1724. PU: function (name) {
  1725. var arg = this.GetArgument(name);
  1726. var tex = CE(arg).Parse('pu');
  1727. this.string = tex + this.string.substr(this.i); this.i = 0;
  1728. }
  1729. });
  1730. //
  1731. // Indicate that the extension is ready
  1732. //
  1733. MathJax.Hub.Startup.signal.Post("TeX mhchem Ready");
  1734. });
  1735. MathJax.Ajax.loadComplete("[mhchem]/mhchem.js");