www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

mhchem.mjs (75560B)


      1 import katex from '../katex.mjs';
      2 
      3 /* eslint-disable */
      4 
      5 /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */
      6 
      7 /* vim: set ts=2 et sw=2 tw=80: */
      8 
      9 /*************************************************************
     10  *
     11  *  KaTeX mhchem.js
     12  *
     13  *  This file implements a KaTeX version of mhchem version 3.3.0.
     14  *  It is adapted from MathJax/extensions/TeX/mhchem.js
     15  *  It differs from the MathJax version as follows:
     16  *    1. The interface is changed so that it can be called from KaTeX, not MathJax.
     17  *    2. \rlap and \llap are replaced with \mathrlap and \mathllap.
     18  *    3. Four lines of code are edited in order to use \raisebox instead of \raise.
     19  *    4. The reaction arrow code is simplified. All reaction arrows are rendered
     20  *       using KaTeX extensible arrows instead of building non-extensible arrows.
     21  *    5. \tripledash vertical alignment is slightly adjusted.
     22  *
     23  *    This code, as other KaTeX code, is released under the MIT license.
     24  * 
     25  * /*************************************************************
     26  *
     27  *  MathJax/extensions/TeX/mhchem.js
     28  *
     29  *  Implements the \ce command for handling chemical formulas
     30  *  from the mhchem LaTeX package.
     31  *
     32  *  ---------------------------------------------------------------------
     33  *
     34  *  Copyright (c) 2011-2015 The MathJax Consortium
     35  *  Copyright (c) 2015-2018 Martin Hensel
     36  *
     37  *  Licensed under the Apache License, Version 2.0 (the "License");
     38  *  you may not use this file except in compliance with the License.
     39  *  You may obtain a copy of the License at
     40  *
     41  *      http://www.apache.org/licenses/LICENSE-2.0
     42  *
     43  *  Unless required by applicable law or agreed to in writing, software
     44  *  distributed under the License is distributed on an "AS IS" BASIS,
     45  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     46  *  See the License for the specific language governing permissions and
     47  *  limitations under the License.
     48  */
     49 //
     50 // Coding Style
     51 //   - use '' for identifiers that can by minified/uglified
     52 //   - use "" for strings that need to stay untouched
     53 // version: "3.3.0" for MathJax and KaTeX
     54 // Add \ce, \pu, and \tripledash to the KaTeX macros.
     55 katex.__defineMacro("\\ce", function (context) {
     56   return chemParse(context.consumeArgs(1)[0], "ce");
     57 });
     58 
     59 katex.__defineMacro("\\pu", function (context) {
     60   return chemParse(context.consumeArgs(1)[0], "pu");
     61 }); //  Needed for \bond for the ~ forms
     62 //  Raise by 2.56mu, not 2mu. We're raising a hyphen-minus, U+002D, not 
     63 //  a mathematical minus, U+2212. So we need that extra 0.56.
     64 
     65 
     66 katex.__defineMacro("\\tripledash", "{\\vphantom{-}\\raisebox{2.56mu}{$\\mkern2mu" + "\\tiny\\text{-}\\mkern1mu\\text{-}\\mkern1mu\\text{-}\\mkern2mu$}}");
     67 //  This is the main function for handing the \ce and \pu commands.
     68 //  It takes the argument to \ce or \pu and returns the corresponding TeX string.
     69 //
     70 
     71 var chemParse = function chemParse(tokens, stateMachine) {
     72   // Recreate the argument string from KaTeX's array of tokens.
     73   var str = "";
     74   var expectedLoc = tokens[tokens.length - 1].loc.start;
     75 
     76   for (var i = tokens.length - 1; i >= 0; i--) {
     77     if (tokens[i].loc.start > expectedLoc) {
     78       // context.consumeArgs has eaten a space.
     79       str += " ";
     80       expectedLoc = tokens[i].loc.start;
     81     }
     82 
     83     str += tokens[i].text;
     84     expectedLoc += tokens[i].text.length;
     85   }
     86 
     87   var tex = texify.go(mhchemParser.go(str, stateMachine));
     88   return tex;
     89 }; //
     90 // Core parser for mhchem syntax  (recursive)
     91 //
     92 
     93 /** @type {MhchemParser} */
     94 
     95 
     96 var mhchemParser = {
     97   //
     98   // Parses mchem \ce syntax
     99   //
    100   // Call like
    101   //   go("H2O");
    102   //
    103   go: function go(input, stateMachine) {
    104     if (!input) {
    105       return [];
    106     }
    107 
    108     if (stateMachine === undefined) {
    109       stateMachine = 'ce';
    110     }
    111 
    112     var state = '0'; //
    113     // String buffers for parsing:
    114     //
    115     // buffer.a == amount
    116     // buffer.o == element
    117     // buffer.b == left-side superscript
    118     // buffer.p == left-side subscript
    119     // buffer.q == right-side subscript
    120     // buffer.d == right-side superscript
    121     //
    122     // buffer.r == arrow
    123     // buffer.rdt == arrow, script above, type
    124     // buffer.rd == arrow, script above, content
    125     // buffer.rqt == arrow, script below, type
    126     // buffer.rq == arrow, script below, content
    127     //
    128     // buffer.text_
    129     // buffer.rm
    130     // etc.
    131     //
    132     // buffer.parenthesisLevel == int, starting at 0
    133     // buffer.sb == bool, space before
    134     // buffer.beginsWithBond == bool
    135     //
    136     // These letters are also used as state names.
    137     //
    138     // Other states:
    139     // 0 == begin of main part (arrow/operator unlikely)
    140     // 1 == next entity
    141     // 2 == next entity (arrow/operator unlikely)
    142     // 3 == next atom
    143     // c == macro
    144     //
    145 
    146     /** @type {Buffer} */
    147 
    148     var buffer = {};
    149     buffer['parenthesisLevel'] = 0;
    150     input = input.replace(/\n/g, " ");
    151     input = input.replace(/[\u2212\u2013\u2014\u2010]/g, "-");
    152     input = input.replace(/[\u2026]/g, "..."); //
    153     // Looks through mhchemParser.transitions, to execute a matching action
    154     // (recursive)
    155     //
    156 
    157     var lastInput;
    158     var watchdog = 10;
    159     /** @type {ParserOutput[]} */
    160 
    161     var output = [];
    162 
    163     while (true) {
    164       if (lastInput !== input) {
    165         watchdog = 10;
    166         lastInput = input;
    167       } else {
    168         watchdog--;
    169       } //
    170       // Find actions in transition table
    171       //
    172 
    173 
    174       var machine = mhchemParser.stateMachines[stateMachine];
    175       var t = machine.transitions[state] || machine.transitions['*'];
    176 
    177       iterateTransitions: for (var i = 0; i < t.length; i++) {
    178         var matches = mhchemParser.patterns.match_(t[i].pattern, input);
    179 
    180         if (matches) {
    181           //
    182           // Execute actions
    183           //
    184           var task = t[i].task;
    185 
    186           for (var iA = 0; iA < task.action_.length; iA++) {
    187             var o; //
    188             // Find and execute action
    189             //
    190 
    191             if (machine.actions[task.action_[iA].type_]) {
    192               o = machine.actions[task.action_[iA].type_](buffer, matches.match_, task.action_[iA].option);
    193             } else if (mhchemParser.actions[task.action_[iA].type_]) {
    194               o = mhchemParser.actions[task.action_[iA].type_](buffer, matches.match_, task.action_[iA].option);
    195             } else {
    196               throw ["MhchemBugA", "mhchem bug A. Please report. (" + task.action_[iA].type_ + ")"]; // Trying to use non-existing action
    197             } //
    198             // Add output
    199             //
    200 
    201 
    202             mhchemParser.concatArray(output, o);
    203           } //
    204           // Set next state,
    205           // Shorten input,
    206           // Continue with next character
    207           //   (= apply only one transition per position)
    208           //
    209 
    210 
    211           state = task.nextState || state;
    212 
    213           if (input.length > 0) {
    214             if (!task.revisit) {
    215               input = matches.remainder;
    216             }
    217 
    218             if (!task.toContinue) {
    219               break iterateTransitions;
    220             }
    221           } else {
    222             return output;
    223           }
    224         }
    225       } //
    226       // Prevent infinite loop
    227       //
    228 
    229 
    230       if (watchdog <= 0) {
    231         throw ["MhchemBugU", "mhchem bug U. Please report."]; // Unexpected character
    232       }
    233     }
    234   },
    235   concatArray: function concatArray(a, b) {
    236     if (b) {
    237       if (Array.isArray(b)) {
    238         for (var iB = 0; iB < b.length; iB++) {
    239           a.push(b[iB]);
    240         }
    241       } else {
    242         a.push(b);
    243       }
    244     }
    245   },
    246   patterns: {
    247     //
    248     // Matching patterns
    249     // either regexps or function that return null or {match_:"a", remainder:"bc"}
    250     //
    251     patterns: {
    252       // property names must not look like integers ("2") for correct property traversal order, later on
    253       'empty': /^$/,
    254       'else': /^./,
    255       'else2': /^./,
    256       'space': /^\s/,
    257       'space A': /^\s(?=[A-Z\\$])/,
    258       'space$': /^\s$/,
    259       'a-z': /^[a-z]/,
    260       'x': /^x/,
    261       'x$': /^x$/,
    262       'i$': /^i$/,
    263       '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]))))+/,
    264       '\\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]))/,
    265       'one lowercase latin letter $': /^(?:([a-z])(?:$|[^a-zA-Z]))$/,
    266       '$one lowercase latin letter$ $': /^\$(?:([a-z])(?:$|[^a-zA-Z]))\$$/,
    267       '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]))$/,
    268       'digits': /^[0-9]+/,
    269       '-9.,9': /^[+\-]?(?:[0-9]+(?:[,.][0-9]+)?|[0-9]*(?:\.[0-9]+))/,
    270       '-9.,9 no missing 0': /^[+\-]?[0-9]+(?:[.,][0-9]+)?/,
    271       '(-)(9.,9)(e)(99)': function e99(input) {
    272         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]+\}))?/);
    273 
    274         if (m && m[0]) {
    275           return {
    276             match_: m.splice(1),
    277             remainder: input.substr(m[0].length)
    278           };
    279         }
    280 
    281         return null;
    282       },
    283       '(-)(9)^(-9)': function _(input) {
    284         var m = input.match(/^(\+\-|\+\/\-|\+|\-|\\pm\s?)?([0-9]+(?:[,.][0-9]+)?|[0-9]*(?:\.[0-9]+)?)\^([+\-]?[0-9]+|\{[+\-]?[0-9]+\})/);
    285 
    286         if (m && m[0]) {
    287           return {
    288             match_: m.splice(1),
    289             remainder: input.substr(m[0].length)
    290           };
    291         }
    292 
    293         return null;
    294       },
    295       'state of aggregation $': function stateOfAggregation$(input) {
    296         // ... or crystal system
    297         var a = mhchemParser.patterns.findObserveGroups(input, "", /^\([a-z]{1,3}(?=[\),])/, ")", ""); // (aq), (aq,$\infty$), (aq, sat)
    298 
    299         if (a && a.remainder.match(/^($|[\s,;\)\]\}])/)) {
    300           return a;
    301         } //  AND end of 'phrase'
    302 
    303 
    304         var m = input.match(/^(?:\((?:\\ca\s?)?\$[amothc]\$\))/); // OR crystal system ($o$) (\ca$c$)
    305 
    306         if (m) {
    307           return {
    308             match_: m[0],
    309             remainder: input.substr(m[0].length)
    310           };
    311         }
    312 
    313         return null;
    314       },
    315       '_{(state of aggregation)}$': /^_\{(\([a-z]{1,3}\))\}/,
    316       '{[(': /^(?:\\\{|\[|\()/,
    317       ')]}': /^(?:\)|\]|\\\})/,
    318       ', ': /^[,;]\s*/,
    319       ',': /^[,;]/,
    320       '.': /^[.]/,
    321       '. ': /^([.\u22C5\u00B7\u2022])\s*/,
    322       '...': /^\.\.\.(?=$|[^.])/,
    323       '* ': /^([*])\s*/,
    324       '^{(...)}': function _(input) {
    325         return mhchemParser.patterns.findObserveGroups(input, "^{", "", "", "}");
    326       },
    327       '^($...$)': function $$(input) {
    328         return mhchemParser.patterns.findObserveGroups(input, "^", "$", "$", "");
    329       },
    330       '^a': /^\^([0-9]+|[^\\_])/,
    331       '^\\x{}{}': function x(input) {
    332         return mhchemParser.patterns.findObserveGroups(input, "^", /^\\[a-zA-Z]+\{/, "}", "", "", "{", "}", "", true);
    333       },
    334       '^\\x{}': function x(input) {
    335         return mhchemParser.patterns.findObserveGroups(input, "^", /^\\[a-zA-Z]+\{/, "}", "");
    336       },
    337       '^\\x': /^\^(\\[a-zA-Z]+)\s*/,
    338       '^(-1)': /^\^(-?\d+)/,
    339       '\'': /^'/,
    340       '_{(...)}': function _(input) {
    341         return mhchemParser.patterns.findObserveGroups(input, "_{", "", "", "}");
    342       },
    343       '_($...$)': function _$$(input) {
    344         return mhchemParser.patterns.findObserveGroups(input, "_", "$", "$", "");
    345       },
    346       '_9': /^_([+\-]?[0-9]+|[^\\])/,
    347       '_\\x{}{}': function _X(input) {
    348         return mhchemParser.patterns.findObserveGroups(input, "_", /^\\[a-zA-Z]+\{/, "}", "", "", "{", "}", "", true);
    349       },
    350       '_\\x{}': function _X(input) {
    351         return mhchemParser.patterns.findObserveGroups(input, "_", /^\\[a-zA-Z]+\{/, "}", "");
    352       },
    353       '_\\x': /^_(\\[a-zA-Z]+)\s*/,
    354       '^_': /^(?:\^(?=_)|\_(?=\^)|[\^_]$)/,
    355       '{}': /^\{\}/,
    356       '{...}': function _(input) {
    357         return mhchemParser.patterns.findObserveGroups(input, "", "{", "}", "");
    358       },
    359       '{(...)}': function _(input) {
    360         return mhchemParser.patterns.findObserveGroups(input, "{", "", "", "}");
    361       },
    362       '$...$': function $$(input) {
    363         return mhchemParser.patterns.findObserveGroups(input, "", "$", "$", "");
    364       },
    365       '${(...)}$': function $$(input) {
    366         return mhchemParser.patterns.findObserveGroups(input, "${", "", "", "}$");
    367       },
    368       '$(...)$': function $$(input) {
    369         return mhchemParser.patterns.findObserveGroups(input, "$", "", "", "$");
    370       },
    371       '=<>': /^[=<>]/,
    372       '#': /^[#\u2261]/,
    373       '+': /^\+/,
    374       '-$': /^-(?=[\s_},;\]/]|$|\([a-z]+\))/,
    375       // -space -, -; -] -/ -$ -state-of-aggregation
    376       '-9': /^-(?=[0-9])/,
    377       '- orbital overlap': /^-(?=(?:[spd]|sp)(?:$|[\s,;\)\]\}]))/,
    378       '-': /^-/,
    379       'pm-operator': /^(?:\\pm|\$\\pm\$|\+-|\+\/-)/,
    380       'operator': /^(?:\+|(?:[\-=<>]|<<|>>|\\approx|\$\\approx\$)(?=\s|$|-?[0-9]))/,
    381       'arrowUpDown': /^(?:v|\(v\)|\^|\(\^\))(?=$|[\s,;\)\]\}])/,
    382       '\\bond{(...)}': function bond(input) {
    383         return mhchemParser.patterns.findObserveGroups(input, "\\bond{", "", "", "}");
    384       },
    385       '->': /^(?:<->|<-->|->|<-|<=>>|<<=>|<=>|[\u2192\u27F6\u21CC])/,
    386       'CMT': /^[CMT](?=\[)/,
    387       '[(...)]': function _(input) {
    388         return mhchemParser.patterns.findObserveGroups(input, "[", "", "", "]");
    389       },
    390       '1st-level escape': /^(&|\\\\|\\hline)\s*/,
    391       '\\,': /^(?:\\[,\ ;:])/,
    392       // \\x - but output no space before
    393       '\\x{}{}': function x(input) {
    394         return mhchemParser.patterns.findObserveGroups(input, "", /^\\[a-zA-Z]+\{/, "}", "", "", "{", "}", "", true);
    395       },
    396       '\\x{}': function x(input) {
    397         return mhchemParser.patterns.findObserveGroups(input, "", /^\\[a-zA-Z]+\{/, "}", "");
    398       },
    399       '\\ca': /^\\ca(?:\s+|(?![a-zA-Z]))/,
    400       '\\x': /^(?:\\[a-zA-Z]+\s*|\\[_&{}%])/,
    401       'orbital': /^(?:[0-9]{1,2}[spdfgh]|[0-9]{0,2}sp)(?=$|[^a-zA-Z])/,
    402       // only those with numbers in front, because the others will be formatted correctly anyway
    403       'others': /^[\/~|]/,
    404       '\\frac{(...)}': function frac(input) {
    405         return mhchemParser.patterns.findObserveGroups(input, "\\frac{", "", "", "}", "{", "", "", "}");
    406       },
    407       '\\overset{(...)}': function overset(input) {
    408         return mhchemParser.patterns.findObserveGroups(input, "\\overset{", "", "", "}", "{", "", "", "}");
    409       },
    410       '\\underset{(...)}': function underset(input) {
    411         return mhchemParser.patterns.findObserveGroups(input, "\\underset{", "", "", "}", "{", "", "", "}");
    412       },
    413       '\\underbrace{(...)}': function underbrace(input) {
    414         return mhchemParser.patterns.findObserveGroups(input, "\\underbrace{", "", "", "}_", "{", "", "", "}");
    415       },
    416       '\\color{(...)}0': function color0(input) {
    417         return mhchemParser.patterns.findObserveGroups(input, "\\color{", "", "", "}");
    418       },
    419       '\\color{(...)}{(...)}1': function color1(input) {
    420         return mhchemParser.patterns.findObserveGroups(input, "\\color{", "", "", "}", "{", "", "", "}");
    421       },
    422       '\\color(...){(...)}2': function color2(input) {
    423         return mhchemParser.patterns.findObserveGroups(input, "\\color", "\\", "", /^(?=\{)/, "{", "", "", "}");
    424       },
    425       '\\ce{(...)}': function ce(input) {
    426         return mhchemParser.patterns.findObserveGroups(input, "\\ce{", "", "", "}");
    427       },
    428       'oxidation$': /^(?:[+-][IVX]+|\\pm\s*0|\$\\pm\$\s*0)$/,
    429       'd-oxidation$': /^(?:[+-]?\s?[IVX]+|\\pm\s*0|\$\\pm\$\s*0)$/,
    430       // 0 could be oxidation or charge
    431       'roman numeral': /^[IVX]+/,
    432       '1/2$': /^[+\-]?(?:[0-9]+|\$[a-z]\$|[a-z])\/[0-9]+(?:\$[a-z]\$|[a-z])?$/,
    433       'amount': function amount(input) {
    434         var match; // e.g. 2, 0.5, 1/2, -2, n/2, +;  $a$ could be added later in parsing
    435 
    436         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))/);
    437 
    438         if (match) {
    439           return {
    440             match_: match[0],
    441             remainder: input.substr(match[0].length)
    442           };
    443         }
    444 
    445         var a = mhchemParser.patterns.findObserveGroups(input, "", "$", "$", "");
    446 
    447         if (a) {
    448           // e.g. $2n-1$, $-$
    449           match = a.match_.match(/^\$(?:\(?[+\-]?(?:[0-9]*[a-z]?[+\-])?[0-9]*[a-z](?:[+\-][0-9]*[a-z]?)?\)?|\+|-)\$$/);
    450 
    451           if (match) {
    452             return {
    453               match_: match[0],
    454               remainder: input.substr(match[0].length)
    455             };
    456           }
    457         }
    458 
    459         return null;
    460       },
    461       'amount2': function amount2(input) {
    462         return this['amount'](input);
    463       },
    464       '(KV letters),': /^(?:[A-Z][a-z]{0,2}|i)(?=,)/,
    465       'formula$': function formula$(input) {
    466         if (input.match(/^\([a-z]+\)$/)) {
    467           return null;
    468         } // state of aggregation = no formula
    469 
    470 
    471         var match = input.match(/^(?:[a-z]|(?:[0-9\ \+\-\,\.\(\)]+[a-z])+[0-9\ \+\-\,\.\(\)]*|(?:[a-z][0-9\ \+\-\,\.\(\)]+)+[a-z]?)$/);
    472 
    473         if (match) {
    474           return {
    475             match_: match[0],
    476             remainder: input.substr(match[0].length)
    477           };
    478         }
    479 
    480         return null;
    481       },
    482       'uprightEntities': /^(?:pH|pOH|pC|pK|iPr|iBu)(?=$|[^a-zA-Z])/,
    483       '/': /^\s*(\/)\s*/,
    484       '//': /^\s*(\/\/)\s*/,
    485       '*': /^\s*[*.]\s*/
    486     },
    487     findObserveGroups: function findObserveGroups(input, begExcl, begIncl, endIncl, endExcl, beg2Excl, beg2Incl, end2Incl, end2Excl, combine) {
    488       /** @type {{(input: string, pattern: string | RegExp): string | string[] | null;}} */
    489       var _match = function _match(input, pattern) {
    490         if (typeof pattern === "string") {
    491           if (input.indexOf(pattern) !== 0) {
    492             return null;
    493           }
    494 
    495           return pattern;
    496         } else {
    497           var match = input.match(pattern);
    498 
    499           if (!match) {
    500             return null;
    501           }
    502 
    503           return match[0];
    504         }
    505       };
    506       /** @type {{(input: string, i: number, endChars: string | RegExp): {endMatchBegin: number, endMatchEnd: number} | null;}} */
    507 
    508 
    509       var _findObserveGroups = function _findObserveGroups(input, i, endChars) {
    510         var braces = 0;
    511 
    512         while (i < input.length) {
    513           var a = input.charAt(i);
    514 
    515           var match = _match(input.substr(i), endChars);
    516 
    517           if (match !== null && braces === 0) {
    518             return {
    519               endMatchBegin: i,
    520               endMatchEnd: i + match.length
    521             };
    522           } else if (a === "{") {
    523             braces++;
    524           } else if (a === "}") {
    525             if (braces === 0) {
    526               throw ["ExtraCloseMissingOpen", "Extra close brace or missing open brace"];
    527             } else {
    528               braces--;
    529             }
    530           }
    531 
    532           i++;
    533         }
    534 
    535         if (braces > 0) {
    536           return null;
    537         }
    538 
    539         return null;
    540       };
    541 
    542       var match = _match(input, begExcl);
    543 
    544       if (match === null) {
    545         return null;
    546       }
    547 
    548       input = input.substr(match.length);
    549       match = _match(input, begIncl);
    550 
    551       if (match === null) {
    552         return null;
    553       }
    554 
    555       var e = _findObserveGroups(input, match.length, endIncl || endExcl);
    556 
    557       if (e === null) {
    558         return null;
    559       }
    560 
    561       var match1 = input.substring(0, endIncl ? e.endMatchEnd : e.endMatchBegin);
    562 
    563       if (!(beg2Excl || beg2Incl)) {
    564         return {
    565           match_: match1,
    566           remainder: input.substr(e.endMatchEnd)
    567         };
    568       } else {
    569         var group2 = this.findObserveGroups(input.substr(e.endMatchEnd), beg2Excl, beg2Incl, end2Incl, end2Excl);
    570 
    571         if (group2 === null) {
    572           return null;
    573         }
    574         /** @type {string[]} */
    575 
    576 
    577         var matchRet = [match1, group2.match_];
    578         return {
    579           match_: combine ? matchRet.join("") : matchRet,
    580           remainder: group2.remainder
    581         };
    582       }
    583     },
    584     //
    585     // Matching function
    586     // e.g. match("a", input) will look for the regexp called "a" and see if it matches
    587     // returns null or {match_:"a", remainder:"bc"}
    588     //
    589     match_: function match_(m, input) {
    590       var pattern = mhchemParser.patterns.patterns[m];
    591 
    592       if (pattern === undefined) {
    593         throw ["MhchemBugP", "mhchem bug P. Please report. (" + m + ")"]; // Trying to use non-existing pattern
    594       } else if (typeof pattern === "function") {
    595         return mhchemParser.patterns.patterns[m](input); // cannot use cached var pattern here, because some pattern functions need this===mhchemParser
    596       } else {
    597         // RegExp
    598         var match = input.match(pattern);
    599 
    600         if (match) {
    601           var mm;
    602 
    603           if (match[2]) {
    604             mm = [match[1], match[2]];
    605           } else if (match[1]) {
    606             mm = match[1];
    607           } else {
    608             mm = match[0];
    609           }
    610 
    611           return {
    612             match_: mm,
    613             remainder: input.substr(match[0].length)
    614           };
    615         }
    616 
    617         return null;
    618       }
    619     }
    620   },
    621   //
    622   // Generic state machine actions
    623   //
    624   actions: {
    625     'a=': function a(buffer, m) {
    626       buffer.a = (buffer.a || "") + m;
    627     },
    628     'b=': function b(buffer, m) {
    629       buffer.b = (buffer.b || "") + m;
    630     },
    631     'p=': function p(buffer, m) {
    632       buffer.p = (buffer.p || "") + m;
    633     },
    634     'o=': function o(buffer, m) {
    635       buffer.o = (buffer.o || "") + m;
    636     },
    637     'q=': function q(buffer, m) {
    638       buffer.q = (buffer.q || "") + m;
    639     },
    640     'd=': function d(buffer, m) {
    641       buffer.d = (buffer.d || "") + m;
    642     },
    643     'rm=': function rm(buffer, m) {
    644       buffer.rm = (buffer.rm || "") + m;
    645     },
    646     'text=': function text(buffer, m) {
    647       buffer.text_ = (buffer.text_ || "") + m;
    648     },
    649     'insert': function insert(buffer, m, a) {
    650       return {
    651         type_: a
    652       };
    653     },
    654     'insert+p1': function insertP1(buffer, m, a) {
    655       return {
    656         type_: a,
    657         p1: m
    658       };
    659     },
    660     'insert+p1+p2': function insertP1P2(buffer, m, a) {
    661       return {
    662         type_: a,
    663         p1: m[0],
    664         p2: m[1]
    665       };
    666     },
    667     'copy': function copy(buffer, m) {
    668       return m;
    669     },
    670     'rm': function rm(buffer, m) {
    671       return {
    672         type_: 'rm',
    673         p1: m || ""
    674       };
    675     },
    676     'text': function text(buffer, m) {
    677       return mhchemParser.go(m, 'text');
    678     },
    679     '{text}': function text(buffer, m) {
    680       var ret = ["{"];
    681       mhchemParser.concatArray(ret, mhchemParser.go(m, 'text'));
    682       ret.push("}");
    683       return ret;
    684     },
    685     'tex-math': function texMath(buffer, m) {
    686       return mhchemParser.go(m, 'tex-math');
    687     },
    688     'tex-math tight': function texMathTight(buffer, m) {
    689       return mhchemParser.go(m, 'tex-math tight');
    690     },
    691     'bond': function bond(buffer, m, k) {
    692       return {
    693         type_: 'bond',
    694         kind_: k || m
    695       };
    696     },
    697     'color0-output': function color0Output(buffer, m) {
    698       return {
    699         type_: 'color0',
    700         color: m[0]
    701       };
    702     },
    703     'ce': function ce(buffer, m) {
    704       return mhchemParser.go(m);
    705     },
    706     '1/2': function _(buffer, m) {
    707       /** @type {ParserOutput[]} */
    708       var ret = [];
    709 
    710       if (m.match(/^[+\-]/)) {
    711         ret.push(m.substr(0, 1));
    712         m = m.substr(1);
    713       }
    714 
    715       var n = m.match(/^([0-9]+|\$[a-z]\$|[a-z])\/([0-9]+)(\$[a-z]\$|[a-z])?$/);
    716       n[1] = n[1].replace(/\$/g, "");
    717       ret.push({
    718         type_: 'frac',
    719         p1: n[1],
    720         p2: n[2]
    721       });
    722 
    723       if (n[3]) {
    724         n[3] = n[3].replace(/\$/g, "");
    725         ret.push({
    726           type_: 'tex-math',
    727           p1: n[3]
    728         });
    729       }
    730 
    731       return ret;
    732     },
    733     '9,9': function _(buffer, m) {
    734       return mhchemParser.go(m, '9,9');
    735     }
    736   },
    737   //
    738   // createTransitions
    739   // convert  { 'letter': { 'state': { action_: 'output' } } }  to  { 'state' => [ { pattern: 'letter', task: { action_: [{type_: 'output'}] } } ] }
    740   // with expansion of 'a|b' to 'a' and 'b' (at 2 places)
    741   //
    742   createTransitions: function createTransitions(o) {
    743     var pattern, state;
    744     /** @type {string[]} */
    745 
    746     var stateArray;
    747     var i; //
    748     // 1. Collect all states
    749     //
    750 
    751     /** @type {Transitions} */
    752 
    753     var transitions = {};
    754 
    755     for (pattern in o) {
    756       for (state in o[pattern]) {
    757         stateArray = state.split("|");
    758         o[pattern][state].stateArray = stateArray;
    759 
    760         for (i = 0; i < stateArray.length; i++) {
    761           transitions[stateArray[i]] = [];
    762         }
    763       }
    764     } //
    765     // 2. Fill states
    766     //
    767 
    768 
    769     for (pattern in o) {
    770       for (state in o[pattern]) {
    771         stateArray = o[pattern][state].stateArray || [];
    772 
    773         for (i = 0; i < stateArray.length; i++) {
    774           //
    775           // 2a. Normalize actions into array:  'text=' ==> [{type_:'text='}]
    776           // (Note to myself: Resolving the function here would be problematic. It would need .bind (for *this*) and currying (for *option*).)
    777           //
    778 
    779           /** @type {any} */
    780           var p = o[pattern][state];
    781 
    782           if (p.action_) {
    783             p.action_ = [].concat(p.action_);
    784 
    785             for (var k = 0; k < p.action_.length; k++) {
    786               if (typeof p.action_[k] === "string") {
    787                 p.action_[k] = {
    788                   type_: p.action_[k]
    789                 };
    790               }
    791             }
    792           } else {
    793             p.action_ = [];
    794           } //
    795           // 2.b Multi-insert
    796           //
    797 
    798 
    799           var patternArray = pattern.split("|");
    800 
    801           for (var j = 0; j < patternArray.length; j++) {
    802             if (stateArray[i] === '*') {
    803               // insert into all
    804               for (var t in transitions) {
    805                 transitions[t].push({
    806                   pattern: patternArray[j],
    807                   task: p
    808                 });
    809               }
    810             } else {
    811               transitions[stateArray[i]].push({
    812                 pattern: patternArray[j],
    813                 task: p
    814               });
    815             }
    816           }
    817         }
    818       }
    819     }
    820 
    821     return transitions;
    822   },
    823   stateMachines: {}
    824 }; //
    825 // Definition of state machines
    826 //
    827 
    828 mhchemParser.stateMachines = {
    829   //
    830   // \ce state machines
    831   //
    832   //#region ce
    833   'ce': {
    834     // main parser
    835     transitions: mhchemParser.createTransitions({
    836       'empty': {
    837         '*': {
    838           action_: 'output'
    839         }
    840       },
    841       'else': {
    842         '0|1|2': {
    843           action_: 'beginsWithBond=false',
    844           revisit: true,
    845           toContinue: true
    846         }
    847       },
    848       'oxidation$': {
    849         '0': {
    850           action_: 'oxidation-output'
    851         }
    852       },
    853       'CMT': {
    854         'r': {
    855           action_: 'rdt=',
    856           nextState: 'rt'
    857         },
    858         'rd': {
    859           action_: 'rqt=',
    860           nextState: 'rdt'
    861         }
    862       },
    863       'arrowUpDown': {
    864         '0|1|2|as': {
    865           action_: ['sb=false', 'output', 'operator'],
    866           nextState: '1'
    867         }
    868       },
    869       'uprightEntities': {
    870         '0|1|2': {
    871           action_: ['o=', 'output'],
    872           nextState: '1'
    873         }
    874       },
    875       'orbital': {
    876         '0|1|2|3': {
    877           action_: 'o=',
    878           nextState: 'o'
    879         }
    880       },
    881       '->': {
    882         '0|1|2|3': {
    883           action_: 'r=',
    884           nextState: 'r'
    885         },
    886         'a|as': {
    887           action_: ['output', 'r='],
    888           nextState: 'r'
    889         },
    890         '*': {
    891           action_: ['output', 'r='],
    892           nextState: 'r'
    893         }
    894       },
    895       '+': {
    896         'o': {
    897           action_: 'd= kv',
    898           nextState: 'd'
    899         },
    900         'd|D': {
    901           action_: 'd=',
    902           nextState: 'd'
    903         },
    904         'q': {
    905           action_: 'd=',
    906           nextState: 'qd'
    907         },
    908         'qd|qD': {
    909           action_: 'd=',
    910           nextState: 'qd'
    911         },
    912         'dq': {
    913           action_: ['output', 'd='],
    914           nextState: 'd'
    915         },
    916         '3': {
    917           action_: ['sb=false', 'output', 'operator'],
    918           nextState: '0'
    919         }
    920       },
    921       'amount': {
    922         '0|2': {
    923           action_: 'a=',
    924           nextState: 'a'
    925         }
    926       },
    927       'pm-operator': {
    928         '0|1|2|a|as': {
    929           action_: ['sb=false', 'output', {
    930             type_: 'operator',
    931             option: '\\pm'
    932           }],
    933           nextState: '0'
    934         }
    935       },
    936       'operator': {
    937         '0|1|2|a|as': {
    938           action_: ['sb=false', 'output', 'operator'],
    939           nextState: '0'
    940         }
    941       },
    942       '-$': {
    943         'o|q': {
    944           action_: ['charge or bond', 'output'],
    945           nextState: 'qd'
    946         },
    947         'd': {
    948           action_: 'd=',
    949           nextState: 'd'
    950         },
    951         'D': {
    952           action_: ['output', {
    953             type_: 'bond',
    954             option: "-"
    955           }],
    956           nextState: '3'
    957         },
    958         'q': {
    959           action_: 'd=',
    960           nextState: 'qd'
    961         },
    962         'qd': {
    963           action_: 'd=',
    964           nextState: 'qd'
    965         },
    966         'qD|dq': {
    967           action_: ['output', {
    968             type_: 'bond',
    969             option: "-"
    970           }],
    971           nextState: '3'
    972         }
    973       },
    974       '-9': {
    975         '3|o': {
    976           action_: ['output', {
    977             type_: 'insert',
    978             option: 'hyphen'
    979           }],
    980           nextState: '3'
    981         }
    982       },
    983       '- orbital overlap': {
    984         'o': {
    985           action_: ['output', {
    986             type_: 'insert',
    987             option: 'hyphen'
    988           }],
    989           nextState: '2'
    990         },
    991         'd': {
    992           action_: ['output', {
    993             type_: 'insert',
    994             option: 'hyphen'
    995           }],
    996           nextState: '2'
    997         }
    998       },
    999       '-': {
   1000         '0|1|2': {
   1001           action_: [{
   1002             type_: 'output',
   1003             option: 1
   1004           }, 'beginsWithBond=true', {
   1005             type_: 'bond',
   1006             option: "-"
   1007           }],
   1008           nextState: '3'
   1009         },
   1010         '3': {
   1011           action_: {
   1012             type_: 'bond',
   1013             option: "-"
   1014           }
   1015         },
   1016         'a': {
   1017           action_: ['output', {
   1018             type_: 'insert',
   1019             option: 'hyphen'
   1020           }],
   1021           nextState: '2'
   1022         },
   1023         'as': {
   1024           action_: [{
   1025             type_: 'output',
   1026             option: 2
   1027           }, {
   1028             type_: 'bond',
   1029             option: "-"
   1030           }],
   1031           nextState: '3'
   1032         },
   1033         'b': {
   1034           action_: 'b='
   1035         },
   1036         'o': {
   1037           action_: {
   1038             type_: '- after o/d',
   1039             option: false
   1040           },
   1041           nextState: '2'
   1042         },
   1043         'q': {
   1044           action_: {
   1045             type_: '- after o/d',
   1046             option: false
   1047           },
   1048           nextState: '2'
   1049         },
   1050         'd|qd|dq': {
   1051           action_: {
   1052             type_: '- after o/d',
   1053             option: true
   1054           },
   1055           nextState: '2'
   1056         },
   1057         'D|qD|p': {
   1058           action_: ['output', {
   1059             type_: 'bond',
   1060             option: "-"
   1061           }],
   1062           nextState: '3'
   1063         }
   1064       },
   1065       'amount2': {
   1066         '1|3': {
   1067           action_: 'a=',
   1068           nextState: 'a'
   1069         }
   1070       },
   1071       'letters': {
   1072         '0|1|2|3|a|as|b|p|bp|o': {
   1073           action_: 'o=',
   1074           nextState: 'o'
   1075         },
   1076         'q|dq': {
   1077           action_: ['output', 'o='],
   1078           nextState: 'o'
   1079         },
   1080         'd|D|qd|qD': {
   1081           action_: 'o after d',
   1082           nextState: 'o'
   1083         }
   1084       },
   1085       'digits': {
   1086         'o': {
   1087           action_: 'q=',
   1088           nextState: 'q'
   1089         },
   1090         'd|D': {
   1091           action_: 'q=',
   1092           nextState: 'dq'
   1093         },
   1094         'q': {
   1095           action_: ['output', 'o='],
   1096           nextState: 'o'
   1097         },
   1098         'a': {
   1099           action_: 'o=',
   1100           nextState: 'o'
   1101         }
   1102       },
   1103       'space A': {
   1104         'b|p|bp': {}
   1105       },
   1106       'space': {
   1107         'a': {
   1108           nextState: 'as'
   1109         },
   1110         '0': {
   1111           action_: 'sb=false'
   1112         },
   1113         '1|2': {
   1114           action_: 'sb=true'
   1115         },
   1116         'r|rt|rd|rdt|rdq': {
   1117           action_: 'output',
   1118           nextState: '0'
   1119         },
   1120         '*': {
   1121           action_: ['output', 'sb=true'],
   1122           nextState: '1'
   1123         }
   1124       },
   1125       '1st-level escape': {
   1126         '1|2': {
   1127           action_: ['output', {
   1128             type_: 'insert+p1',
   1129             option: '1st-level escape'
   1130           }]
   1131         },
   1132         '*': {
   1133           action_: ['output', {
   1134             type_: 'insert+p1',
   1135             option: '1st-level escape'
   1136           }],
   1137           nextState: '0'
   1138         }
   1139       },
   1140       '[(...)]': {
   1141         'r|rt': {
   1142           action_: 'rd=',
   1143           nextState: 'rd'
   1144         },
   1145         'rd|rdt': {
   1146           action_: 'rq=',
   1147           nextState: 'rdq'
   1148         }
   1149       },
   1150       '...': {
   1151         'o|d|D|dq|qd|qD': {
   1152           action_: ['output', {
   1153             type_: 'bond',
   1154             option: "..."
   1155           }],
   1156           nextState: '3'
   1157         },
   1158         '*': {
   1159           action_: [{
   1160             type_: 'output',
   1161             option: 1
   1162           }, {
   1163             type_: 'insert',
   1164             option: 'ellipsis'
   1165           }],
   1166           nextState: '1'
   1167         }
   1168       },
   1169       '. |* ': {
   1170         '*': {
   1171           action_: ['output', {
   1172             type_: 'insert',
   1173             option: 'addition compound'
   1174           }],
   1175           nextState: '1'
   1176         }
   1177       },
   1178       'state of aggregation $': {
   1179         '*': {
   1180           action_: ['output', 'state of aggregation'],
   1181           nextState: '1'
   1182         }
   1183       },
   1184       '{[(': {
   1185         'a|as|o': {
   1186           action_: ['o=', 'output', 'parenthesisLevel++'],
   1187           nextState: '2'
   1188         },
   1189         '0|1|2|3': {
   1190           action_: ['o=', 'output', 'parenthesisLevel++'],
   1191           nextState: '2'
   1192         },
   1193         '*': {
   1194           action_: ['output', 'o=', 'output', 'parenthesisLevel++'],
   1195           nextState: '2'
   1196         }
   1197       },
   1198       ')]}': {
   1199         '0|1|2|3|b|p|bp|o': {
   1200           action_: ['o=', 'parenthesisLevel--'],
   1201           nextState: 'o'
   1202         },
   1203         'a|as|d|D|q|qd|qD|dq': {
   1204           action_: ['output', 'o=', 'parenthesisLevel--'],
   1205           nextState: 'o'
   1206         }
   1207       },
   1208       ', ': {
   1209         '*': {
   1210           action_: ['output', 'comma'],
   1211           nextState: '0'
   1212         }
   1213       },
   1214       '^_': {
   1215         // ^ and _ without a sensible argument
   1216         '*': {}
   1217       },
   1218       '^{(...)}|^($...$)': {
   1219         '0|1|2|as': {
   1220           action_: 'b=',
   1221           nextState: 'b'
   1222         },
   1223         'p': {
   1224           action_: 'b=',
   1225           nextState: 'bp'
   1226         },
   1227         '3|o': {
   1228           action_: 'd= kv',
   1229           nextState: 'D'
   1230         },
   1231         'q': {
   1232           action_: 'd=',
   1233           nextState: 'qD'
   1234         },
   1235         'd|D|qd|qD|dq': {
   1236           action_: ['output', 'd='],
   1237           nextState: 'D'
   1238         }
   1239       },
   1240       '^a|^\\x{}{}|^\\x{}|^\\x|\'': {
   1241         '0|1|2|as': {
   1242           action_: 'b=',
   1243           nextState: 'b'
   1244         },
   1245         'p': {
   1246           action_: 'b=',
   1247           nextState: 'bp'
   1248         },
   1249         '3|o': {
   1250           action_: 'd= kv',
   1251           nextState: 'd'
   1252         },
   1253         'q': {
   1254           action_: 'd=',
   1255           nextState: 'qd'
   1256         },
   1257         'd|qd|D|qD': {
   1258           action_: 'd='
   1259         },
   1260         'dq': {
   1261           action_: ['output', 'd='],
   1262           nextState: 'd'
   1263         }
   1264       },
   1265       '_{(state of aggregation)}$': {
   1266         'd|D|q|qd|qD|dq': {
   1267           action_: ['output', 'q='],
   1268           nextState: 'q'
   1269         }
   1270       },
   1271       '_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x': {
   1272         '0|1|2|as': {
   1273           action_: 'p=',
   1274           nextState: 'p'
   1275         },
   1276         'b': {
   1277           action_: 'p=',
   1278           nextState: 'bp'
   1279         },
   1280         '3|o': {
   1281           action_: 'q=',
   1282           nextState: 'q'
   1283         },
   1284         'd|D': {
   1285           action_: 'q=',
   1286           nextState: 'dq'
   1287         },
   1288         'q|qd|qD|dq': {
   1289           action_: ['output', 'q='],
   1290           nextState: 'q'
   1291         }
   1292       },
   1293       '=<>': {
   1294         '0|1|2|3|a|as|o|q|d|D|qd|qD|dq': {
   1295           action_: [{
   1296             type_: 'output',
   1297             option: 2
   1298           }, 'bond'],
   1299           nextState: '3'
   1300         }
   1301       },
   1302       '#': {
   1303         '0|1|2|3|a|as|o': {
   1304           action_: [{
   1305             type_: 'output',
   1306             option: 2
   1307           }, {
   1308             type_: 'bond',
   1309             option: "#"
   1310           }],
   1311           nextState: '3'
   1312         }
   1313       },
   1314       '{}': {
   1315         '*': {
   1316           action_: {
   1317             type_: 'output',
   1318             option: 1
   1319           },
   1320           nextState: '1'
   1321         }
   1322       },
   1323       '{...}': {
   1324         '0|1|2|3|a|as|b|p|bp': {
   1325           action_: 'o=',
   1326           nextState: 'o'
   1327         },
   1328         'o|d|D|q|qd|qD|dq': {
   1329           action_: ['output', 'o='],
   1330           nextState: 'o'
   1331         }
   1332       },
   1333       '$...$': {
   1334         'a': {
   1335           action_: 'a='
   1336         },
   1337         // 2$n$
   1338         '0|1|2|3|as|b|p|bp|o': {
   1339           action_: 'o=',
   1340           nextState: 'o'
   1341         },
   1342         // not 'amount'
   1343         'as|o': {
   1344           action_: 'o='
   1345         },
   1346         'q|d|D|qd|qD|dq': {
   1347           action_: ['output', 'o='],
   1348           nextState: 'o'
   1349         }
   1350       },
   1351       '\\bond{(...)}': {
   1352         '*': {
   1353           action_: [{
   1354             type_: 'output',
   1355             option: 2
   1356           }, 'bond'],
   1357           nextState: "3"
   1358         }
   1359       },
   1360       '\\frac{(...)}': {
   1361         '*': {
   1362           action_: [{
   1363             type_: 'output',
   1364             option: 1
   1365           }, 'frac-output'],
   1366           nextState: '3'
   1367         }
   1368       },
   1369       '\\overset{(...)}': {
   1370         '*': {
   1371           action_: [{
   1372             type_: 'output',
   1373             option: 2
   1374           }, 'overset-output'],
   1375           nextState: '3'
   1376         }
   1377       },
   1378       '\\underset{(...)}': {
   1379         '*': {
   1380           action_: [{
   1381             type_: 'output',
   1382             option: 2
   1383           }, 'underset-output'],
   1384           nextState: '3'
   1385         }
   1386       },
   1387       '\\underbrace{(...)}': {
   1388         '*': {
   1389           action_: [{
   1390             type_: 'output',
   1391             option: 2
   1392           }, 'underbrace-output'],
   1393           nextState: '3'
   1394         }
   1395       },
   1396       '\\color{(...)}{(...)}1|\\color(...){(...)}2': {
   1397         '*': {
   1398           action_: [{
   1399             type_: 'output',
   1400             option: 2
   1401           }, 'color-output'],
   1402           nextState: '3'
   1403         }
   1404       },
   1405       '\\color{(...)}0': {
   1406         '*': {
   1407           action_: [{
   1408             type_: 'output',
   1409             option: 2
   1410           }, 'color0-output']
   1411         }
   1412       },
   1413       '\\ce{(...)}': {
   1414         '*': {
   1415           action_: [{
   1416             type_: 'output',
   1417             option: 2
   1418           }, 'ce'],
   1419           nextState: '3'
   1420         }
   1421       },
   1422       '\\,': {
   1423         '*': {
   1424           action_: [{
   1425             type_: 'output',
   1426             option: 1
   1427           }, 'copy'],
   1428           nextState: '1'
   1429         }
   1430       },
   1431       '\\x{}{}|\\x{}|\\x': {
   1432         '0|1|2|3|a|as|b|p|bp|o|c0': {
   1433           action_: ['o=', 'output'],
   1434           nextState: '3'
   1435         },
   1436         '*': {
   1437           action_: ['output', 'o=', 'output'],
   1438           nextState: '3'
   1439         }
   1440       },
   1441       'others': {
   1442         '*': {
   1443           action_: [{
   1444             type_: 'output',
   1445             option: 1
   1446           }, 'copy'],
   1447           nextState: '3'
   1448         }
   1449       },
   1450       'else2': {
   1451         'a': {
   1452           action_: 'a to o',
   1453           nextState: 'o',
   1454           revisit: true
   1455         },
   1456         'as': {
   1457           action_: ['output', 'sb=true'],
   1458           nextState: '1',
   1459           revisit: true
   1460         },
   1461         'r|rt|rd|rdt|rdq': {
   1462           action_: ['output'],
   1463           nextState: '0',
   1464           revisit: true
   1465         },
   1466         '*': {
   1467           action_: ['output', 'copy'],
   1468           nextState: '3'
   1469         }
   1470       }
   1471     }),
   1472     actions: {
   1473       'o after d': function oAfterD(buffer, m) {
   1474         var ret;
   1475 
   1476         if ((buffer.d || "").match(/^[0-9]+$/)) {
   1477           var tmp = buffer.d;
   1478           buffer.d = undefined;
   1479           ret = this['output'](buffer);
   1480           buffer.b = tmp;
   1481         } else {
   1482           ret = this['output'](buffer);
   1483         }
   1484 
   1485         mhchemParser.actions['o='](buffer, m);
   1486         return ret;
   1487       },
   1488       'd= kv': function dKv(buffer, m) {
   1489         buffer.d = m;
   1490         buffer.dType = 'kv';
   1491       },
   1492       'charge or bond': function chargeOrBond(buffer, m) {
   1493         if (buffer['beginsWithBond']) {
   1494           /** @type {ParserOutput[]} */
   1495           var ret = [];
   1496           mhchemParser.concatArray(ret, this['output'](buffer));
   1497           mhchemParser.concatArray(ret, mhchemParser.actions['bond'](buffer, m, "-"));
   1498           return ret;
   1499         } else {
   1500           buffer.d = m;
   1501         }
   1502       },
   1503       '- after o/d': function afterOD(buffer, m, isAfterD) {
   1504         var c1 = mhchemParser.patterns.match_('orbital', buffer.o || "");
   1505         var c2 = mhchemParser.patterns.match_('one lowercase greek letter $', buffer.o || "");
   1506         var c3 = mhchemParser.patterns.match_('one lowercase latin letter $', buffer.o || "");
   1507         var c4 = mhchemParser.patterns.match_('$one lowercase latin letter$ $', buffer.o || "");
   1508         var hyphenFollows = m === "-" && (c1 && c1.remainder === "" || c2 || c3 || c4);
   1509 
   1510         if (hyphenFollows && !buffer.a && !buffer.b && !buffer.p && !buffer.d && !buffer.q && !c1 && c3) {
   1511           buffer.o = '$' + buffer.o + '$';
   1512         }
   1513         /** @type {ParserOutput[]} */
   1514 
   1515 
   1516         var ret = [];
   1517 
   1518         if (hyphenFollows) {
   1519           mhchemParser.concatArray(ret, this['output'](buffer));
   1520           ret.push({
   1521             type_: 'hyphen'
   1522           });
   1523         } else {
   1524           c1 = mhchemParser.patterns.match_('digits', buffer.d || "");
   1525 
   1526           if (isAfterD && c1 && c1.remainder === '') {
   1527             mhchemParser.concatArray(ret, mhchemParser.actions['d='](buffer, m));
   1528             mhchemParser.concatArray(ret, this['output'](buffer));
   1529           } else {
   1530             mhchemParser.concatArray(ret, this['output'](buffer));
   1531             mhchemParser.concatArray(ret, mhchemParser.actions['bond'](buffer, m, "-"));
   1532           }
   1533         }
   1534 
   1535         return ret;
   1536       },
   1537       'a to o': function aToO(buffer) {
   1538         buffer.o = buffer.a;
   1539         buffer.a = undefined;
   1540       },
   1541       'sb=true': function sbTrue(buffer) {
   1542         buffer.sb = true;
   1543       },
   1544       'sb=false': function sbFalse(buffer) {
   1545         buffer.sb = false;
   1546       },
   1547       'beginsWithBond=true': function beginsWithBondTrue(buffer) {
   1548         buffer['beginsWithBond'] = true;
   1549       },
   1550       'beginsWithBond=false': function beginsWithBondFalse(buffer) {
   1551         buffer['beginsWithBond'] = false;
   1552       },
   1553       'parenthesisLevel++': function parenthesisLevel(buffer) {
   1554         buffer['parenthesisLevel']++;
   1555       },
   1556       'parenthesisLevel--': function parenthesisLevel(buffer) {
   1557         buffer['parenthesisLevel']--;
   1558       },
   1559       'state of aggregation': function stateOfAggregation(buffer, m) {
   1560         return {
   1561           type_: 'state of aggregation',
   1562           p1: mhchemParser.go(m, 'o')
   1563         };
   1564       },
   1565       'comma': function comma(buffer, m) {
   1566         var a = m.replace(/\s*$/, '');
   1567         var withSpace = a !== m;
   1568 
   1569         if (withSpace && buffer['parenthesisLevel'] === 0) {
   1570           return {
   1571             type_: 'comma enumeration L',
   1572             p1: a
   1573           };
   1574         } else {
   1575           return {
   1576             type_: 'comma enumeration M',
   1577             p1: a
   1578           };
   1579         }
   1580       },
   1581       'output': function output(buffer, m, entityFollows) {
   1582         // entityFollows:
   1583         //   undefined = if we have nothing else to output, also ignore the just read space (buffer.sb)
   1584         //   1 = an entity follows, never omit the space if there was one just read before (can only apply to state 1)
   1585         //   2 = 1 + the entity can have an amount, so output a\, instead of converting it to o (can only apply to states a|as)
   1586 
   1587         /** @type {ParserOutput | ParserOutput[]} */
   1588         var ret;
   1589 
   1590         if (!buffer.r) {
   1591           ret = [];
   1592 
   1593           if (!buffer.a && !buffer.b && !buffer.p && !buffer.o && !buffer.q && !buffer.d && !entityFollows) ; else {
   1594             if (buffer.sb) {
   1595               ret.push({
   1596                 type_: 'entitySkip'
   1597               });
   1598             }
   1599 
   1600             if (!buffer.o && !buffer.q && !buffer.d && !buffer.b && !buffer.p && entityFollows !== 2) {
   1601               buffer.o = buffer.a;
   1602               buffer.a = undefined;
   1603             } else if (!buffer.o && !buffer.q && !buffer.d && (buffer.b || buffer.p)) {
   1604               buffer.o = buffer.a;
   1605               buffer.d = buffer.b;
   1606               buffer.q = buffer.p;
   1607               buffer.a = buffer.b = buffer.p = undefined;
   1608             } else {
   1609               if (buffer.o && buffer.dType === 'kv' && mhchemParser.patterns.match_('d-oxidation$', buffer.d || "")) {
   1610                 buffer.dType = 'oxidation';
   1611               } else if (buffer.o && buffer.dType === 'kv' && !buffer.q) {
   1612                 buffer.dType = undefined;
   1613               }
   1614             }
   1615 
   1616             ret.push({
   1617               type_: 'chemfive',
   1618               a: mhchemParser.go(buffer.a, 'a'),
   1619               b: mhchemParser.go(buffer.b, 'bd'),
   1620               p: mhchemParser.go(buffer.p, 'pq'),
   1621               o: mhchemParser.go(buffer.o, 'o'),
   1622               q: mhchemParser.go(buffer.q, 'pq'),
   1623               d: mhchemParser.go(buffer.d, buffer.dType === 'oxidation' ? 'oxidation' : 'bd'),
   1624               dType: buffer.dType
   1625             });
   1626           }
   1627         } else {
   1628           // r
   1629 
   1630           /** @type {ParserOutput[]} */
   1631           var rd;
   1632 
   1633           if (buffer.rdt === 'M') {
   1634             rd = mhchemParser.go(buffer.rd, 'tex-math');
   1635           } else if (buffer.rdt === 'T') {
   1636             rd = [{
   1637               type_: 'text',
   1638               p1: buffer.rd || ""
   1639             }];
   1640           } else {
   1641             rd = mhchemParser.go(buffer.rd);
   1642           }
   1643           /** @type {ParserOutput[]} */
   1644 
   1645 
   1646           var rq;
   1647 
   1648           if (buffer.rqt === 'M') {
   1649             rq = mhchemParser.go(buffer.rq, 'tex-math');
   1650           } else if (buffer.rqt === 'T') {
   1651             rq = [{
   1652               type_: 'text',
   1653               p1: buffer.rq || ""
   1654             }];
   1655           } else {
   1656             rq = mhchemParser.go(buffer.rq);
   1657           }
   1658 
   1659           ret = {
   1660             type_: 'arrow',
   1661             r: buffer.r,
   1662             rd: rd,
   1663             rq: rq
   1664           };
   1665         }
   1666 
   1667         for (var p in buffer) {
   1668           if (p !== 'parenthesisLevel' && p !== 'beginsWithBond') {
   1669             delete buffer[p];
   1670           }
   1671         }
   1672 
   1673         return ret;
   1674       },
   1675       'oxidation-output': function oxidationOutput(buffer, m) {
   1676         var ret = ["{"];
   1677         mhchemParser.concatArray(ret, mhchemParser.go(m, 'oxidation'));
   1678         ret.push("}");
   1679         return ret;
   1680       },
   1681       'frac-output': function fracOutput(buffer, m) {
   1682         return {
   1683           type_: 'frac-ce',
   1684           p1: mhchemParser.go(m[0]),
   1685           p2: mhchemParser.go(m[1])
   1686         };
   1687       },
   1688       'overset-output': function oversetOutput(buffer, m) {
   1689         return {
   1690           type_: 'overset',
   1691           p1: mhchemParser.go(m[0]),
   1692           p2: mhchemParser.go(m[1])
   1693         };
   1694       },
   1695       'underset-output': function undersetOutput(buffer, m) {
   1696         return {
   1697           type_: 'underset',
   1698           p1: mhchemParser.go(m[0]),
   1699           p2: mhchemParser.go(m[1])
   1700         };
   1701       },
   1702       'underbrace-output': function underbraceOutput(buffer, m) {
   1703         return {
   1704           type_: 'underbrace',
   1705           p1: mhchemParser.go(m[0]),
   1706           p2: mhchemParser.go(m[1])
   1707         };
   1708       },
   1709       'color-output': function colorOutput(buffer, m) {
   1710         return {
   1711           type_: 'color',
   1712           color1: m[0],
   1713           color2: mhchemParser.go(m[1])
   1714         };
   1715       },
   1716       'r=': function r(buffer, m) {
   1717         buffer.r = m;
   1718       },
   1719       'rdt=': function rdt(buffer, m) {
   1720         buffer.rdt = m;
   1721       },
   1722       'rd=': function rd(buffer, m) {
   1723         buffer.rd = m;
   1724       },
   1725       'rqt=': function rqt(buffer, m) {
   1726         buffer.rqt = m;
   1727       },
   1728       'rq=': function rq(buffer, m) {
   1729         buffer.rq = m;
   1730       },
   1731       'operator': function operator(buffer, m, p1) {
   1732         return {
   1733           type_: 'operator',
   1734           kind_: p1 || m
   1735         };
   1736       }
   1737     }
   1738   },
   1739   'a': {
   1740     transitions: mhchemParser.createTransitions({
   1741       'empty': {
   1742         '*': {}
   1743       },
   1744       '1/2$': {
   1745         '0': {
   1746           action_: '1/2'
   1747         }
   1748       },
   1749       'else': {
   1750         '0': {
   1751           nextState: '1',
   1752           revisit: true
   1753         }
   1754       },
   1755       '$(...)$': {
   1756         '*': {
   1757           action_: 'tex-math tight',
   1758           nextState: '1'
   1759         }
   1760       },
   1761       ',': {
   1762         '*': {
   1763           action_: {
   1764             type_: 'insert',
   1765             option: 'commaDecimal'
   1766           }
   1767         }
   1768       },
   1769       'else2': {
   1770         '*': {
   1771           action_: 'copy'
   1772         }
   1773       }
   1774     }),
   1775     actions: {}
   1776   },
   1777   'o': {
   1778     transitions: mhchemParser.createTransitions({
   1779       'empty': {
   1780         '*': {}
   1781       },
   1782       '1/2$': {
   1783         '0': {
   1784           action_: '1/2'
   1785         }
   1786       },
   1787       'else': {
   1788         '0': {
   1789           nextState: '1',
   1790           revisit: true
   1791         }
   1792       },
   1793       'letters': {
   1794         '*': {
   1795           action_: 'rm'
   1796         }
   1797       },
   1798       '\\ca': {
   1799         '*': {
   1800           action_: {
   1801             type_: 'insert',
   1802             option: 'circa'
   1803           }
   1804         }
   1805       },
   1806       '\\x{}{}|\\x{}|\\x': {
   1807         '*': {
   1808           action_: 'copy'
   1809         }
   1810       },
   1811       '${(...)}$|$(...)$': {
   1812         '*': {
   1813           action_: 'tex-math'
   1814         }
   1815       },
   1816       '{(...)}': {
   1817         '*': {
   1818           action_: '{text}'
   1819         }
   1820       },
   1821       'else2': {
   1822         '*': {
   1823           action_: 'copy'
   1824         }
   1825       }
   1826     }),
   1827     actions: {}
   1828   },
   1829   'text': {
   1830     transitions: mhchemParser.createTransitions({
   1831       'empty': {
   1832         '*': {
   1833           action_: 'output'
   1834         }
   1835       },
   1836       '{...}': {
   1837         '*': {
   1838           action_: 'text='
   1839         }
   1840       },
   1841       '${(...)}$|$(...)$': {
   1842         '*': {
   1843           action_: 'tex-math'
   1844         }
   1845       },
   1846       '\\greek': {
   1847         '*': {
   1848           action_: ['output', 'rm']
   1849         }
   1850       },
   1851       '\\,|\\x{}{}|\\x{}|\\x': {
   1852         '*': {
   1853           action_: ['output', 'copy']
   1854         }
   1855       },
   1856       'else': {
   1857         '*': {
   1858           action_: 'text='
   1859         }
   1860       }
   1861     }),
   1862     actions: {
   1863       'output': function output(buffer) {
   1864         if (buffer.text_) {
   1865           /** @type {ParserOutput} */
   1866           var ret = {
   1867             type_: 'text',
   1868             p1: buffer.text_
   1869           };
   1870 
   1871           for (var p in buffer) {
   1872             delete buffer[p];
   1873           }
   1874 
   1875           return ret;
   1876         }
   1877       }
   1878     }
   1879   },
   1880   'pq': {
   1881     transitions: mhchemParser.createTransitions({
   1882       'empty': {
   1883         '*': {}
   1884       },
   1885       'state of aggregation $': {
   1886         '*': {
   1887           action_: 'state of aggregation'
   1888         }
   1889       },
   1890       'i$': {
   1891         '0': {
   1892           nextState: '!f',
   1893           revisit: true
   1894         }
   1895       },
   1896       '(KV letters),': {
   1897         '0': {
   1898           action_: 'rm',
   1899           nextState: '0'
   1900         }
   1901       },
   1902       'formula$': {
   1903         '0': {
   1904           nextState: 'f',
   1905           revisit: true
   1906         }
   1907       },
   1908       '1/2$': {
   1909         '0': {
   1910           action_: '1/2'
   1911         }
   1912       },
   1913       'else': {
   1914         '0': {
   1915           nextState: '!f',
   1916           revisit: true
   1917         }
   1918       },
   1919       '${(...)}$|$(...)$': {
   1920         '*': {
   1921           action_: 'tex-math'
   1922         }
   1923       },
   1924       '{(...)}': {
   1925         '*': {
   1926           action_: 'text'
   1927         }
   1928       },
   1929       'a-z': {
   1930         'f': {
   1931           action_: 'tex-math'
   1932         }
   1933       },
   1934       'letters': {
   1935         '*': {
   1936           action_: 'rm'
   1937         }
   1938       },
   1939       '-9.,9': {
   1940         '*': {
   1941           action_: '9,9'
   1942         }
   1943       },
   1944       ',': {
   1945         '*': {
   1946           action_: {
   1947             type_: 'insert+p1',
   1948             option: 'comma enumeration S'
   1949           }
   1950         }
   1951       },
   1952       '\\color{(...)}{(...)}1|\\color(...){(...)}2': {
   1953         '*': {
   1954           action_: 'color-output'
   1955         }
   1956       },
   1957       '\\color{(...)}0': {
   1958         '*': {
   1959           action_: 'color0-output'
   1960         }
   1961       },
   1962       '\\ce{(...)}': {
   1963         '*': {
   1964           action_: 'ce'
   1965         }
   1966       },
   1967       '\\,|\\x{}{}|\\x{}|\\x': {
   1968         '*': {
   1969           action_: 'copy'
   1970         }
   1971       },
   1972       'else2': {
   1973         '*': {
   1974           action_: 'copy'
   1975         }
   1976       }
   1977     }),
   1978     actions: {
   1979       'state of aggregation': function stateOfAggregation(buffer, m) {
   1980         return {
   1981           type_: 'state of aggregation subscript',
   1982           p1: mhchemParser.go(m, 'o')
   1983         };
   1984       },
   1985       'color-output': function colorOutput(buffer, m) {
   1986         return {
   1987           type_: 'color',
   1988           color1: m[0],
   1989           color2: mhchemParser.go(m[1], 'pq')
   1990         };
   1991       }
   1992     }
   1993   },
   1994   'bd': {
   1995     transitions: mhchemParser.createTransitions({
   1996       'empty': {
   1997         '*': {}
   1998       },
   1999       'x$': {
   2000         '0': {
   2001           nextState: '!f',
   2002           revisit: true
   2003         }
   2004       },
   2005       'formula$': {
   2006         '0': {
   2007           nextState: 'f',
   2008           revisit: true
   2009         }
   2010       },
   2011       'else': {
   2012         '0': {
   2013           nextState: '!f',
   2014           revisit: true
   2015         }
   2016       },
   2017       '-9.,9 no missing 0': {
   2018         '*': {
   2019           action_: '9,9'
   2020         }
   2021       },
   2022       '.': {
   2023         '*': {
   2024           action_: {
   2025             type_: 'insert',
   2026             option: 'electron dot'
   2027           }
   2028         }
   2029       },
   2030       'a-z': {
   2031         'f': {
   2032           action_: 'tex-math'
   2033         }
   2034       },
   2035       'x': {
   2036         '*': {
   2037           action_: {
   2038             type_: 'insert',
   2039             option: 'KV x'
   2040           }
   2041         }
   2042       },
   2043       'letters': {
   2044         '*': {
   2045           action_: 'rm'
   2046         }
   2047       },
   2048       '\'': {
   2049         '*': {
   2050           action_: {
   2051             type_: 'insert',
   2052             option: 'prime'
   2053           }
   2054         }
   2055       },
   2056       '${(...)}$|$(...)$': {
   2057         '*': {
   2058           action_: 'tex-math'
   2059         }
   2060       },
   2061       '{(...)}': {
   2062         '*': {
   2063           action_: 'text'
   2064         }
   2065       },
   2066       '\\color{(...)}{(...)}1|\\color(...){(...)}2': {
   2067         '*': {
   2068           action_: 'color-output'
   2069         }
   2070       },
   2071       '\\color{(...)}0': {
   2072         '*': {
   2073           action_: 'color0-output'
   2074         }
   2075       },
   2076       '\\ce{(...)}': {
   2077         '*': {
   2078           action_: 'ce'
   2079         }
   2080       },
   2081       '\\,|\\x{}{}|\\x{}|\\x': {
   2082         '*': {
   2083           action_: 'copy'
   2084         }
   2085       },
   2086       'else2': {
   2087         '*': {
   2088           action_: 'copy'
   2089         }
   2090       }
   2091     }),
   2092     actions: {
   2093       'color-output': function colorOutput(buffer, m) {
   2094         return {
   2095           type_: 'color',
   2096           color1: m[0],
   2097           color2: mhchemParser.go(m[1], 'bd')
   2098         };
   2099       }
   2100     }
   2101   },
   2102   'oxidation': {
   2103     transitions: mhchemParser.createTransitions({
   2104       'empty': {
   2105         '*': {}
   2106       },
   2107       'roman numeral': {
   2108         '*': {
   2109           action_: 'roman-numeral'
   2110         }
   2111       },
   2112       '${(...)}$|$(...)$': {
   2113         '*': {
   2114           action_: 'tex-math'
   2115         }
   2116       },
   2117       'else': {
   2118         '*': {
   2119           action_: 'copy'
   2120         }
   2121       }
   2122     }),
   2123     actions: {
   2124       'roman-numeral': function romanNumeral(buffer, m) {
   2125         return {
   2126           type_: 'roman numeral',
   2127           p1: m || ""
   2128         };
   2129       }
   2130     }
   2131   },
   2132   'tex-math': {
   2133     transitions: mhchemParser.createTransitions({
   2134       'empty': {
   2135         '*': {
   2136           action_: 'output'
   2137         }
   2138       },
   2139       '\\ce{(...)}': {
   2140         '*': {
   2141           action_: ['output', 'ce']
   2142         }
   2143       },
   2144       '{...}|\\,|\\x{}{}|\\x{}|\\x': {
   2145         '*': {
   2146           action_: 'o='
   2147         }
   2148       },
   2149       'else': {
   2150         '*': {
   2151           action_: 'o='
   2152         }
   2153       }
   2154     }),
   2155     actions: {
   2156       'output': function output(buffer) {
   2157         if (buffer.o) {
   2158           /** @type {ParserOutput} */
   2159           var ret = {
   2160             type_: 'tex-math',
   2161             p1: buffer.o
   2162           };
   2163 
   2164           for (var p in buffer) {
   2165             delete buffer[p];
   2166           }
   2167 
   2168           return ret;
   2169         }
   2170       }
   2171     }
   2172   },
   2173   'tex-math tight': {
   2174     transitions: mhchemParser.createTransitions({
   2175       'empty': {
   2176         '*': {
   2177           action_: 'output'
   2178         }
   2179       },
   2180       '\\ce{(...)}': {
   2181         '*': {
   2182           action_: ['output', 'ce']
   2183         }
   2184       },
   2185       '{...}|\\,|\\x{}{}|\\x{}|\\x': {
   2186         '*': {
   2187           action_: 'o='
   2188         }
   2189       },
   2190       '-|+': {
   2191         '*': {
   2192           action_: 'tight operator'
   2193         }
   2194       },
   2195       'else': {
   2196         '*': {
   2197           action_: 'o='
   2198         }
   2199       }
   2200     }),
   2201     actions: {
   2202       'tight operator': function tightOperator(buffer, m) {
   2203         buffer.o = (buffer.o || "") + "{" + m + "}";
   2204       },
   2205       'output': function output(buffer) {
   2206         if (buffer.o) {
   2207           /** @type {ParserOutput} */
   2208           var ret = {
   2209             type_: 'tex-math',
   2210             p1: buffer.o
   2211           };
   2212 
   2213           for (var p in buffer) {
   2214             delete buffer[p];
   2215           }
   2216 
   2217           return ret;
   2218         }
   2219       }
   2220     }
   2221   },
   2222   '9,9': {
   2223     transitions: mhchemParser.createTransitions({
   2224       'empty': {
   2225         '*': {}
   2226       },
   2227       ',': {
   2228         '*': {
   2229           action_: 'comma'
   2230         }
   2231       },
   2232       'else': {
   2233         '*': {
   2234           action_: 'copy'
   2235         }
   2236       }
   2237     }),
   2238     actions: {
   2239       'comma': function comma() {
   2240         return {
   2241           type_: 'commaDecimal'
   2242         };
   2243       }
   2244     }
   2245   },
   2246   //#endregion
   2247   //
   2248   // \pu state machines
   2249   //
   2250   //#region pu
   2251   'pu': {
   2252     transitions: mhchemParser.createTransitions({
   2253       'empty': {
   2254         '*': {
   2255           action_: 'output'
   2256         }
   2257       },
   2258       'space$': {
   2259         '*': {
   2260           action_: ['output', 'space']
   2261         }
   2262       },
   2263       '{[(|)]}': {
   2264         '0|a': {
   2265           action_: 'copy'
   2266         }
   2267       },
   2268       '(-)(9)^(-9)': {
   2269         '0': {
   2270           action_: 'number^',
   2271           nextState: 'a'
   2272         }
   2273       },
   2274       '(-)(9.,9)(e)(99)': {
   2275         '0': {
   2276           action_: 'enumber',
   2277           nextState: 'a'
   2278         }
   2279       },
   2280       'space': {
   2281         '0|a': {}
   2282       },
   2283       'pm-operator': {
   2284         '0|a': {
   2285           action_: {
   2286             type_: 'operator',
   2287             option: '\\pm'
   2288           },
   2289           nextState: '0'
   2290         }
   2291       },
   2292       'operator': {
   2293         '0|a': {
   2294           action_: 'copy',
   2295           nextState: '0'
   2296         }
   2297       },
   2298       '//': {
   2299         'd': {
   2300           action_: 'o=',
   2301           nextState: '/'
   2302         }
   2303       },
   2304       '/': {
   2305         'd': {
   2306           action_: 'o=',
   2307           nextState: '/'
   2308         }
   2309       },
   2310       '{...}|else': {
   2311         '0|d': {
   2312           action_: 'd=',
   2313           nextState: 'd'
   2314         },
   2315         'a': {
   2316           action_: ['space', 'd='],
   2317           nextState: 'd'
   2318         },
   2319         '/|q': {
   2320           action_: 'q=',
   2321           nextState: 'q'
   2322         }
   2323       }
   2324     }),
   2325     actions: {
   2326       'enumber': function enumber(buffer, m) {
   2327         /** @type {ParserOutput[]} */
   2328         var ret = [];
   2329 
   2330         if (m[0] === "+-" || m[0] === "+/-") {
   2331           ret.push("\\pm ");
   2332         } else if (m[0]) {
   2333           ret.push(m[0]);
   2334         }
   2335 
   2336         if (m[1]) {
   2337           mhchemParser.concatArray(ret, mhchemParser.go(m[1], 'pu-9,9'));
   2338 
   2339           if (m[2]) {
   2340             if (m[2].match(/[,.]/)) {
   2341               mhchemParser.concatArray(ret, mhchemParser.go(m[2], 'pu-9,9'));
   2342             } else {
   2343               ret.push(m[2]);
   2344             }
   2345           }
   2346 
   2347           m[3] = m[4] || m[3];
   2348 
   2349           if (m[3]) {
   2350             m[3] = m[3].trim();
   2351 
   2352             if (m[3] === "e" || m[3].substr(0, 1) === "*") {
   2353               ret.push({
   2354                 type_: 'cdot'
   2355               });
   2356             } else {
   2357               ret.push({
   2358                 type_: 'times'
   2359               });
   2360             }
   2361           }
   2362         }
   2363 
   2364         if (m[3]) {
   2365           ret.push("10^{" + m[5] + "}");
   2366         }
   2367 
   2368         return ret;
   2369       },
   2370       'number^': function number(buffer, m) {
   2371         /** @type {ParserOutput[]} */
   2372         var ret = [];
   2373 
   2374         if (m[0] === "+-" || m[0] === "+/-") {
   2375           ret.push("\\pm ");
   2376         } else if (m[0]) {
   2377           ret.push(m[0]);
   2378         }
   2379 
   2380         mhchemParser.concatArray(ret, mhchemParser.go(m[1], 'pu-9,9'));
   2381         ret.push("^{" + m[2] + "}");
   2382         return ret;
   2383       },
   2384       'operator': function operator(buffer, m, p1) {
   2385         return {
   2386           type_: 'operator',
   2387           kind_: p1 || m
   2388         };
   2389       },
   2390       'space': function space() {
   2391         return {
   2392           type_: 'pu-space-1'
   2393         };
   2394       },
   2395       'output': function output(buffer) {
   2396         /** @type {ParserOutput | ParserOutput[]} */
   2397         var ret;
   2398         var md = mhchemParser.patterns.match_('{(...)}', buffer.d || "");
   2399 
   2400         if (md && md.remainder === '') {
   2401           buffer.d = md.match_;
   2402         }
   2403 
   2404         var mq = mhchemParser.patterns.match_('{(...)}', buffer.q || "");
   2405 
   2406         if (mq && mq.remainder === '') {
   2407           buffer.q = mq.match_;
   2408         }
   2409 
   2410         if (buffer.d) {
   2411           buffer.d = buffer.d.replace(/\u00B0C|\^oC|\^{o}C/g, "{}^{\\circ}C");
   2412           buffer.d = buffer.d.replace(/\u00B0F|\^oF|\^{o}F/g, "{}^{\\circ}F");
   2413         }
   2414 
   2415         if (buffer.q) {
   2416           // fraction
   2417           buffer.q = buffer.q.replace(/\u00B0C|\^oC|\^{o}C/g, "{}^{\\circ}C");
   2418           buffer.q = buffer.q.replace(/\u00B0F|\^oF|\^{o}F/g, "{}^{\\circ}F");
   2419           var b5 = {
   2420             d: mhchemParser.go(buffer.d, 'pu'),
   2421             q: mhchemParser.go(buffer.q, 'pu')
   2422           };
   2423 
   2424           if (buffer.o === '//') {
   2425             ret = {
   2426               type_: 'pu-frac',
   2427               p1: b5.d,
   2428               p2: b5.q
   2429             };
   2430           } else {
   2431             ret = b5.d;
   2432 
   2433             if (b5.d.length > 1 || b5.q.length > 1) {
   2434               ret.push({
   2435                 type_: ' / '
   2436               });
   2437             } else {
   2438               ret.push({
   2439                 type_: '/'
   2440               });
   2441             }
   2442 
   2443             mhchemParser.concatArray(ret, b5.q);
   2444           }
   2445         } else {
   2446           // no fraction
   2447           ret = mhchemParser.go(buffer.d, 'pu-2');
   2448         }
   2449 
   2450         for (var p in buffer) {
   2451           delete buffer[p];
   2452         }
   2453 
   2454         return ret;
   2455       }
   2456     }
   2457   },
   2458   'pu-2': {
   2459     transitions: mhchemParser.createTransitions({
   2460       'empty': {
   2461         '*': {
   2462           action_: 'output'
   2463         }
   2464       },
   2465       '*': {
   2466         '*': {
   2467           action_: ['output', 'cdot'],
   2468           nextState: '0'
   2469         }
   2470       },
   2471       '\\x': {
   2472         '*': {
   2473           action_: 'rm='
   2474         }
   2475       },
   2476       'space': {
   2477         '*': {
   2478           action_: ['output', 'space'],
   2479           nextState: '0'
   2480         }
   2481       },
   2482       '^{(...)}|^(-1)': {
   2483         '1': {
   2484           action_: '^(-1)'
   2485         }
   2486       },
   2487       '-9.,9': {
   2488         '0': {
   2489           action_: 'rm=',
   2490           nextState: '0'
   2491         },
   2492         '1': {
   2493           action_: '^(-1)',
   2494           nextState: '0'
   2495         }
   2496       },
   2497       '{...}|else': {
   2498         '*': {
   2499           action_: 'rm=',
   2500           nextState: '1'
   2501         }
   2502       }
   2503     }),
   2504     actions: {
   2505       'cdot': function cdot() {
   2506         return {
   2507           type_: 'tight cdot'
   2508         };
   2509       },
   2510       '^(-1)': function _(buffer, m) {
   2511         buffer.rm += "^{" + m + "}";
   2512       },
   2513       'space': function space() {
   2514         return {
   2515           type_: 'pu-space-2'
   2516         };
   2517       },
   2518       'output': function output(buffer) {
   2519         /** @type {ParserOutput | ParserOutput[]} */
   2520         var ret = [];
   2521 
   2522         if (buffer.rm) {
   2523           var mrm = mhchemParser.patterns.match_('{(...)}', buffer.rm || "");
   2524 
   2525           if (mrm && mrm.remainder === '') {
   2526             ret = mhchemParser.go(mrm.match_, 'pu');
   2527           } else {
   2528             ret = {
   2529               type_: 'rm',
   2530               p1: buffer.rm
   2531             };
   2532           }
   2533         }
   2534 
   2535         for (var p in buffer) {
   2536           delete buffer[p];
   2537         }
   2538 
   2539         return ret;
   2540       }
   2541     }
   2542   },
   2543   'pu-9,9': {
   2544     transitions: mhchemParser.createTransitions({
   2545       'empty': {
   2546         '0': {
   2547           action_: 'output-0'
   2548         },
   2549         'o': {
   2550           action_: 'output-o'
   2551         }
   2552       },
   2553       ',': {
   2554         '0': {
   2555           action_: ['output-0', 'comma'],
   2556           nextState: 'o'
   2557         }
   2558       },
   2559       '.': {
   2560         '0': {
   2561           action_: ['output-0', 'copy'],
   2562           nextState: 'o'
   2563         }
   2564       },
   2565       'else': {
   2566         '*': {
   2567           action_: 'text='
   2568         }
   2569       }
   2570     }),
   2571     actions: {
   2572       'comma': function comma() {
   2573         return {
   2574           type_: 'commaDecimal'
   2575         };
   2576       },
   2577       'output-0': function output0(buffer) {
   2578         /** @type {ParserOutput[]} */
   2579         var ret = [];
   2580         buffer.text_ = buffer.text_ || "";
   2581 
   2582         if (buffer.text_.length > 4) {
   2583           var a = buffer.text_.length % 3;
   2584 
   2585           if (a === 0) {
   2586             a = 3;
   2587           }
   2588 
   2589           for (var i = buffer.text_.length - 3; i > 0; i -= 3) {
   2590             ret.push(buffer.text_.substr(i, 3));
   2591             ret.push({
   2592               type_: '1000 separator'
   2593             });
   2594           }
   2595 
   2596           ret.push(buffer.text_.substr(0, a));
   2597           ret.reverse();
   2598         } else {
   2599           ret.push(buffer.text_);
   2600         }
   2601 
   2602         for (var p in buffer) {
   2603           delete buffer[p];
   2604         }
   2605 
   2606         return ret;
   2607       },
   2608       'output-o': function outputO(buffer) {
   2609         /** @type {ParserOutput[]} */
   2610         var ret = [];
   2611         buffer.text_ = buffer.text_ || "";
   2612 
   2613         if (buffer.text_.length > 4) {
   2614           var a = buffer.text_.length - 3;
   2615 
   2616           for (var i = 0; i < a; i += 3) {
   2617             ret.push(buffer.text_.substr(i, 3));
   2618             ret.push({
   2619               type_: '1000 separator'
   2620             });
   2621           }
   2622 
   2623           ret.push(buffer.text_.substr(i));
   2624         } else {
   2625           ret.push(buffer.text_);
   2626         }
   2627 
   2628         for (var p in buffer) {
   2629           delete buffer[p];
   2630         }
   2631 
   2632         return ret;
   2633       }
   2634     }
   2635   } //#endregion
   2636 
   2637 }; //
   2638 // texify: Take MhchemParser output and convert it to TeX
   2639 //
   2640 
   2641 /** @type {Texify} */
   2642 
   2643 var texify = {
   2644   go: function go(input, isInner) {
   2645     // (recursive, max 4 levels)
   2646     if (!input) {
   2647       return "";
   2648     }
   2649 
   2650     var res = "";
   2651     var cee = false;
   2652 
   2653     for (var i = 0; i < input.length; i++) {
   2654       var inputi = input[i];
   2655 
   2656       if (typeof inputi === "string") {
   2657         res += inputi;
   2658       } else {
   2659         res += texify._go2(inputi);
   2660 
   2661         if (inputi.type_ === '1st-level escape') {
   2662           cee = true;
   2663         }
   2664       }
   2665     }
   2666 
   2667     if (!isInner && !cee && res) {
   2668       res = "{" + res + "}";
   2669     }
   2670 
   2671     return res;
   2672   },
   2673   _goInner: function _goInner(input) {
   2674     if (!input) {
   2675       return input;
   2676     }
   2677 
   2678     return texify.go(input, true);
   2679   },
   2680   _go2: function _go2(buf) {
   2681     /** @type {undefined | string} */
   2682     var res;
   2683 
   2684     switch (buf.type_) {
   2685       case 'chemfive':
   2686         res = "";
   2687         var b5 = {
   2688           a: texify._goInner(buf.a),
   2689           b: texify._goInner(buf.b),
   2690           p: texify._goInner(buf.p),
   2691           o: texify._goInner(buf.o),
   2692           q: texify._goInner(buf.q),
   2693           d: texify._goInner(buf.d)
   2694         }; //
   2695         // a
   2696         //
   2697 
   2698         if (b5.a) {
   2699           if (b5.a.match(/^[+\-]/)) {
   2700             b5.a = "{" + b5.a + "}";
   2701           }
   2702 
   2703           res += b5.a + "\\,";
   2704         } //
   2705         // b and p
   2706         //
   2707 
   2708 
   2709         if (b5.b || b5.p) {
   2710           res += "{\\vphantom{X}}";
   2711           res += "^{\\hphantom{" + (b5.b || "") + "}}_{\\hphantom{" + (b5.p || "") + "}}";
   2712           res += "{\\vphantom{X}}";
   2713           res += "^{\\smash[t]{\\vphantom{2}}\\mathllap{" + (b5.b || "") + "}}";
   2714           res += "_{\\vphantom{2}\\mathllap{\\smash[t]{" + (b5.p || "") + "}}}";
   2715         } //
   2716         // o
   2717         //
   2718 
   2719 
   2720         if (b5.o) {
   2721           if (b5.o.match(/^[+\-]/)) {
   2722             b5.o = "{" + b5.o + "}";
   2723           }
   2724 
   2725           res += b5.o;
   2726         } //
   2727         // q and d
   2728         //
   2729 
   2730 
   2731         if (buf.dType === 'kv') {
   2732           if (b5.d || b5.q) {
   2733             res += "{\\vphantom{X}}";
   2734           }
   2735 
   2736           if (b5.d) {
   2737             res += "^{" + b5.d + "}";
   2738           }
   2739 
   2740           if (b5.q) {
   2741             res += "_{\\smash[t]{" + b5.q + "}}";
   2742           }
   2743         } else if (buf.dType === 'oxidation') {
   2744           if (b5.d) {
   2745             res += "{\\vphantom{X}}";
   2746             res += "^{" + b5.d + "}";
   2747           }
   2748 
   2749           if (b5.q) {
   2750             res += "{\\vphantom{X}}";
   2751             res += "_{\\smash[t]{" + b5.q + "}}";
   2752           }
   2753         } else {
   2754           if (b5.q) {
   2755             res += "{\\vphantom{X}}";
   2756             res += "_{\\smash[t]{" + b5.q + "}}";
   2757           }
   2758 
   2759           if (b5.d) {
   2760             res += "{\\vphantom{X}}";
   2761             res += "^{" + b5.d + "}";
   2762           }
   2763         }
   2764 
   2765         break;
   2766 
   2767       case 'rm':
   2768         res = "\\mathrm{" + buf.p1 + "}";
   2769         break;
   2770 
   2771       case 'text':
   2772         if (buf.p1.match(/[\^_]/)) {
   2773           buf.p1 = buf.p1.replace(" ", "~").replace("-", "\\text{-}");
   2774           res = "\\mathrm{" + buf.p1 + "}";
   2775         } else {
   2776           res = "\\text{" + buf.p1 + "}";
   2777         }
   2778 
   2779         break;
   2780 
   2781       case 'roman numeral':
   2782         res = "\\mathrm{" + buf.p1 + "}";
   2783         break;
   2784 
   2785       case 'state of aggregation':
   2786         res = "\\mskip2mu " + texify._goInner(buf.p1);
   2787         break;
   2788 
   2789       case 'state of aggregation subscript':
   2790         res = "\\mskip1mu " + texify._goInner(buf.p1);
   2791         break;
   2792 
   2793       case 'bond':
   2794         res = texify._getBond(buf.kind_);
   2795 
   2796         if (!res) {
   2797           throw ["MhchemErrorBond", "mhchem Error. Unknown bond type (" + buf.kind_ + ")"];
   2798         }
   2799 
   2800         break;
   2801 
   2802       case 'frac':
   2803         var c = "\\frac{" + buf.p1 + "}{" + buf.p2 + "}";
   2804         res = "\\mathchoice{\\textstyle" + c + "}{" + c + "}{" + c + "}{" + c + "}";
   2805         break;
   2806 
   2807       case 'pu-frac':
   2808         var d = "\\frac{" + texify._goInner(buf.p1) + "}{" + texify._goInner(buf.p2) + "}";
   2809         res = "\\mathchoice{\\textstyle" + d + "}{" + d + "}{" + d + "}{" + d + "}";
   2810         break;
   2811 
   2812       case 'tex-math':
   2813         res = buf.p1 + " ";
   2814         break;
   2815 
   2816       case 'frac-ce':
   2817         res = "\\frac{" + texify._goInner(buf.p1) + "}{" + texify._goInner(buf.p2) + "}";
   2818         break;
   2819 
   2820       case 'overset':
   2821         res = "\\overset{" + texify._goInner(buf.p1) + "}{" + texify._goInner(buf.p2) + "}";
   2822         break;
   2823 
   2824       case 'underset':
   2825         res = "\\underset{" + texify._goInner(buf.p1) + "}{" + texify._goInner(buf.p2) + "}";
   2826         break;
   2827 
   2828       case 'underbrace':
   2829         res = "\\underbrace{" + texify._goInner(buf.p1) + "}_{" + texify._goInner(buf.p2) + "}";
   2830         break;
   2831 
   2832       case 'color':
   2833         res = "{\\color{" + buf.color1 + "}{" + texify._goInner(buf.color2) + "}}";
   2834         break;
   2835 
   2836       case 'color0':
   2837         res = "\\color{" + buf.color + "}";
   2838         break;
   2839 
   2840       case 'arrow':
   2841         var b6 = {
   2842           rd: texify._goInner(buf.rd),
   2843           rq: texify._goInner(buf.rq)
   2844         };
   2845 
   2846         var arrow = "\\x" + texify._getArrow(buf.r);
   2847 
   2848         if (b6.rq) {
   2849           arrow += "[{" + b6.rq + "}]";
   2850         }
   2851 
   2852         if (b6.rd) {
   2853           arrow += "{" + b6.rd + "}";
   2854         } else {
   2855           arrow += "{}";
   2856         }
   2857 
   2858         res = arrow;
   2859         break;
   2860 
   2861       case 'operator':
   2862         res = texify._getOperator(buf.kind_);
   2863         break;
   2864 
   2865       case '1st-level escape':
   2866         res = buf.p1 + " "; // &, \\\\, \\hlin
   2867 
   2868         break;
   2869 
   2870       case 'space':
   2871         res = " ";
   2872         break;
   2873 
   2874       case 'entitySkip':
   2875         res = "~";
   2876         break;
   2877 
   2878       case 'pu-space-1':
   2879         res = "~";
   2880         break;
   2881 
   2882       case 'pu-space-2':
   2883         res = "\\mkern3mu ";
   2884         break;
   2885 
   2886       case '1000 separator':
   2887         res = "\\mkern2mu ";
   2888         break;
   2889 
   2890       case 'commaDecimal':
   2891         res = "{,}";
   2892         break;
   2893 
   2894       case 'comma enumeration L':
   2895         res = "{" + buf.p1 + "}\\mkern6mu ";
   2896         break;
   2897 
   2898       case 'comma enumeration M':
   2899         res = "{" + buf.p1 + "}\\mkern3mu ";
   2900         break;
   2901 
   2902       case 'comma enumeration S':
   2903         res = "{" + buf.p1 + "}\\mkern1mu ";
   2904         break;
   2905 
   2906       case 'hyphen':
   2907         res = "\\text{-}";
   2908         break;
   2909 
   2910       case 'addition compound':
   2911         res = "\\,{\\cdot}\\,";
   2912         break;
   2913 
   2914       case 'electron dot':
   2915         res = "\\mkern1mu \\bullet\\mkern1mu ";
   2916         break;
   2917 
   2918       case 'KV x':
   2919         res = "{\\times}";
   2920         break;
   2921 
   2922       case 'prime':
   2923         res = "\\prime ";
   2924         break;
   2925 
   2926       case 'cdot':
   2927         res = "\\cdot ";
   2928         break;
   2929 
   2930       case 'tight cdot':
   2931         res = "\\mkern1mu{\\cdot}\\mkern1mu ";
   2932         break;
   2933 
   2934       case 'times':
   2935         res = "\\times ";
   2936         break;
   2937 
   2938       case 'circa':
   2939         res = "{\\sim}";
   2940         break;
   2941 
   2942       case '^':
   2943         res = "uparrow";
   2944         break;
   2945 
   2946       case 'v':
   2947         res = "downarrow";
   2948         break;
   2949 
   2950       case 'ellipsis':
   2951         res = "\\ldots ";
   2952         break;
   2953 
   2954       case '/':
   2955         res = "/";
   2956         break;
   2957 
   2958       case ' / ':
   2959         res = "\\,/\\,";
   2960         break;
   2961 
   2962       default:
   2963         throw ["MhchemBugT", "mhchem bug T. Please report."];
   2964       // Missing texify rule or unknown MhchemParser output
   2965     }
   2966     return res;
   2967   },
   2968   _getArrow: function _getArrow(a) {
   2969     switch (a) {
   2970       case "->":
   2971         return "rightarrow";
   2972 
   2973       case "\u2192":
   2974         return "rightarrow";
   2975 
   2976       case "\u27F6":
   2977         return "rightarrow";
   2978 
   2979       case "<-":
   2980         return "leftarrow";
   2981 
   2982       case "<->":
   2983         return "leftrightarrow";
   2984 
   2985       case "<-->":
   2986         return "rightleftarrows";
   2987 
   2988       case "<=>":
   2989         return "rightleftharpoons";
   2990 
   2991       case "\u21CC":
   2992         return "rightleftharpoons";
   2993 
   2994       case "<=>>":
   2995         return "rightequilibrium";
   2996 
   2997       case "<<=>":
   2998         return "leftequilibrium";
   2999 
   3000       default:
   3001         throw ["MhchemBugT", "mhchem bug T. Please report."];
   3002     }
   3003   },
   3004   _getBond: function _getBond(a) {
   3005     switch (a) {
   3006       case "-":
   3007         return "{-}";
   3008 
   3009       case "1":
   3010         return "{-}";
   3011 
   3012       case "=":
   3013         return "{=}";
   3014 
   3015       case "2":
   3016         return "{=}";
   3017 
   3018       case "#":
   3019         return "{\\equiv}";
   3020 
   3021       case "3":
   3022         return "{\\equiv}";
   3023 
   3024       case "~":
   3025         return "{\\tripledash}";
   3026 
   3027       case "~-":
   3028         return "{\\mathrlap{\\raisebox{-.1em}{$-$}}\\raisebox{.1em}{$\\tripledash$}}";
   3029 
   3030       case "~=":
   3031         return "{\\mathrlap{\\raisebox{-.2em}{$-$}}\\mathrlap{\\raisebox{.2em}{$\\tripledash$}}-}";
   3032 
   3033       case "~--":
   3034         return "{\\mathrlap{\\raisebox{-.2em}{$-$}}\\mathrlap{\\raisebox{.2em}{$\\tripledash$}}-}";
   3035 
   3036       case "-~-":
   3037         return "{\\mathrlap{\\raisebox{-.2em}{$-$}}\\mathrlap{\\raisebox{.2em}{$-$}}\\tripledash}";
   3038 
   3039       case "...":
   3040         return "{{\\cdot}{\\cdot}{\\cdot}}";
   3041 
   3042       case "....":
   3043         return "{{\\cdot}{\\cdot}{\\cdot}{\\cdot}}";
   3044 
   3045       case "->":
   3046         return "{\\rightarrow}";
   3047 
   3048       case "<-":
   3049         return "{\\leftarrow}";
   3050 
   3051       case "<":
   3052         return "{<}";
   3053 
   3054       case ">":
   3055         return "{>}";
   3056 
   3057       default:
   3058         throw ["MhchemBugT", "mhchem bug T. Please report."];
   3059     }
   3060   },
   3061   _getOperator: function _getOperator(a) {
   3062     switch (a) {
   3063       case "+":
   3064         return " {}+{} ";
   3065 
   3066       case "-":
   3067         return " {}-{} ";
   3068 
   3069       case "=":
   3070         return " {}={} ";
   3071 
   3072       case "<":
   3073         return " {}<{} ";
   3074 
   3075       case ">":
   3076         return " {}>{} ";
   3077 
   3078       case "<<":
   3079         return " {}\\ll{} ";
   3080 
   3081       case ">>":
   3082         return " {}\\gg{} ";
   3083 
   3084       case "\\pm":
   3085         return " {}\\pm{} ";
   3086 
   3087       case "\\approx":
   3088         return " {}\\approx{} ";
   3089 
   3090       case "$\\approx$":
   3091         return " {}\\approx{} ";
   3092 
   3093       case "v":
   3094         return " \\downarrow{} ";
   3095 
   3096       case "(v)":
   3097         return " \\downarrow{} ";
   3098 
   3099       case "^":
   3100         return " \\uparrow{} ";
   3101 
   3102       case "(^)":
   3103         return " \\uparrow{} ";
   3104 
   3105       default:
   3106         throw ["MhchemBugT", "mhchem bug T. Please report."];
   3107     }
   3108   }
   3109 }; //