Implement mhchemParser in PHP

* mhchemParser v4.2.2 in Typescript was used as blueprint for this:
* https://github.com/mhchem/mhchemParser
* The mhchemParserPHP component tests can be run locally on a machine with PHP without MediaWiki-Environment with the MMLmhchemTestLocal.php
* For the tests it is probably only necessary to review the json files (Mhchemv4mml.json, Mhchemv4tex.json).
* src/TexVC/MHChem/MhchemParser.php contains the basic functionality, Patterns, StateMachines, Texify functions are located in dedicated classes
* added extracted mhchem commands which have intermediately have been introduced to the
  texVC(PHP) grammar

Change-Id: I8cc3d04937b93339e352adc95c85a8a178b8825f
Bug: T329620
这个提交包含在:
Stegmujo 2023-04-21 10:38:11 +00:00 提交者 Moritz Schubotz (physikerwelt)
父节点 8c5a694656
当前提交 6514c9d24e
找不到此签名对应的密钥
GPG 密钥 ID: F803DB146DDF36C3
共有 13 个文件被更改,包括 4751 次插入0 次删除

2
.gitignore vendored
查看文件

@ -1,3 +1,5 @@
src/TexVC/test.4.1.1.php
src/TexVC/test.4.1.2.php
.DS_Store
/nbproject/private/
node_modules/

查看文件

@ -165,6 +165,16 @@ class JsonToMathML extends Maintenance {
}
}
break;
case 4:
// Example file ExamplesNewCommandsMhchem.json
foreach ( $fileData as $entry ) {
$inputF[] = [
"description" => $entry["description"],
"tex" => $entry['tex'],
"type" => $entry['type'],
];
}
break;
}
return $inputF;
}

查看文件

@ -0,0 +1,208 @@
**************************************************************************
*
* mhchemParser.ts
* 4.2.2
*
* Parser for the \ce command and \pu command for MathJax and Co.
*
* mhchem's \ce is a tool for writing beautiful chemical equations easily.
* mhchem's \pu is a tool for writing physical units easily.
*
* -----------------------------------------------------------------------
*
* Copyright 2015-2023 Martin Hensel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* or in file LICENSE.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* -----------------------------------------------------------------------
*
* https://github.com/mhchem/mhchemParser
*
**************************************************************************
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

查看文件

@ -0,0 +1,187 @@
<?php
/**
* Copyright (c) 2023 Johannes Stegmüller
*
* This file is a port of mhchemParser originally authored by Martin Hensel in javascript/typescript.
* The original license for this software can be found in the accompanying LICENSE.mhchemParser-ts.txt file.
*/
declare( strict_types = 1 );
namespace MediaWiki\Extension\Math\TexVC\Mhchem;
use MediaWiki\Logger\LoggerFactory;
use Psr\Log\LoggerInterface;
use RuntimeException;
/**
* Port of mhchemParser v4.2.2 by Martin Hensel (https://github.com/mhchem/mhchemParser)
* from typescript/javascript to PHP.
*
* This class contains the go (¸l.89 in mhchemParser.js)
* and the toTex function (l.39 of mhchemParser.js)
*
* For usage of mhchemParser in PHP instantiate this class and call toTex-Function.
*
* @author Johannes Stegmüller
* @license GPL-2.0-or-later
*/
class MhchemParser {
/** @var MhchemPatterns */
private MhchemPatterns $mhchemPatterns;
/** @var MhchemStateMachines */
private MhchemStateMachines $mhchemStateMachines;
/** @var LoggerInterface */
private $logger;
/** @var int */
private int $debugIndex;
/**
* Instantiate Mhchemparser, required for usage of "toTex" functionality
* @param bool $doLogging debug log internal state changes and input output for each state
*/
public function __construct( bool $doLogging = false ) {
$this->mhchemPatterns = new MhchemPatterns();
$this->mhchemStateMachines = new MhchemStateMachines( $this );
$this->debugIndex = 0;
if ( $doLogging ) {
$this->logger = LoggerFactory::getInstance( 'Math' );
}
}
public function getPatterns(): MhchemPatterns {
return $this->mhchemPatterns;
}
/**
* @param string $input input formula in tex eventually containing chemical environments or physical units
* @param string $type currently ce or pu (physical units)
* @param bool $optimizeMhchemForTexVC optimize the output of mhchem for usage in TexVC, usually extra curlies
* surrounding parameters which specify dimensions
* @return string
*/
public function toTex( $input, $type, bool $optimizeMhchemForTexVC = false ): string {
$parsed = $this->go( $input, $type );
$mhchemTexifiy = new MhchemTexify( $optimizeMhchemForTexVC );
return $mhchemTexifiy->go( $parsed, $type !== "tex" );
}
public function go( $input, $stateMachine ): array {
if ( !MhchemUtil::issetJS( $input ) ) {
return [];
}
if ( !MhchemUtil::issetJS( $stateMachine ) ) {
$stateMachine = 'ce';
}
$state = '0';
$buffer = [];
$buffer['parenthesisLevel'] = 0;
if ( $input != null ) {
$input = preg_replace( "/\n/", "", $input );
$input = preg_replace( "/[\x{2212}\x{2013}\x{2014}\x{2010}]/u", "-", $input );
$input = preg_replace( "/[\x{2026}]/u", "...", $input );
}
// Looks through _mhchemParser.transitions, to execute a matching action
// (recursive)actions
$lastInput = "";
$watchdog = 10;
$output = [];
while ( true ) {
if ( $lastInput !== $input ) {
$watchdog = 10;
$lastInput = $input;
} else {
$watchdog--;
}
// Find actions in transition table
$machine = $this->mhchemStateMachines->stateMachines[$stateMachine];
$t = $machine["transitions"][$state] ?? $machine["transitions"]['*'];
for ( $i = 0; $i < count( $t ); $i++ ) {
$matches = $this->mhchemPatterns->match( $t[$i]["pattern"], $input ?? "" );
if ( $matches ) {
if ( $this->logger ) {
$this->logger->debug( "\n Match at: " . $i . "\tPattern: " . $t[$i]["pattern"] .
"\t State-machine: " . $stateMachine );
}
// Execute actions
$task = $t[$i]["task"];
for ( $iA = 0; $iA < count( $task["action_"] ); $iA++ ) {
$this->debugIndex++;
$o = null;
// Find and execute action
if ( array_key_exists( $task["action_"][$iA]["type_"], $machine["actions"] ) ) {
$option = $task["action_"][$iA]["option"] ?? null; // tbd, setting null ok ?
if ( $this->logger ) {
$this->logger->debug( "\n action: \t" . $task["action_"][$iA]["type_"] );
}
$o = $machine["actions"][$task["action_"][$iA]["type_"]]
( $buffer, $matches["match_"], $option );
} elseif ( array_key_exists( $task["action_"][$iA]["type_"],
$this->mhchemStateMachines->getGenericActions() ) ) {
$option = $task["action_"][$iA]["option"] ?? null;
if ( $this->logger ) {
$this->logger->debug( "\n action: \t" . $task["action_"][$iA]["type_"] );
}
$o = $this->mhchemStateMachines->getGenericActions()
[$task["action_"][$iA]["type_"]]( $buffer, $matches["match_"], $option );
} else {
// Unexpected character
throw new RuntimeException( "MhchemBugA: mhchem bug A. Please report. ("
. $task->action_[$iA]->type_ . ")" );
}
// Add output
MhchemUtil::concatArray( $output, $o );
if ( $this->logger ) {
$this->logger->debug( "\n State: " . $state );
$this->logger->debug( "\n Buffer: " . json_encode( $buffer ) );
$this->logger->debug( "\n Input: " . $input );
$this->logger->debug( "\n Output: " . json_encode( $output ) );
$this->logger->debug( "\n" );
}
}
// Set next state,
// Shorten input,
// Continue with next character concatArray
// (= apply only one transition per position)
$state = $task["nextState"] ?? $state;
if ( $input != null && strlen( $input ) > 0 ) {
if ( !array_key_exists( "revisit", $task ) ) {
$input = $matches["remainder"];
}
if ( !array_key_exists( "toContinue", $task ) ) {
// this breaks the two for loops
break 1;
}
} else {
return $output;
}
}
}
// Prevent infinite loop
if ( $watchdog <= 0 ) {
// Unexpected character
throw new RunTimeException( "MhchemBugU: mhchem-PHP bug U. Please report." );
}
}
}
}

查看文件

@ -0,0 +1,402 @@
<?php
/**
* Copyright (c) 2023 Johannes Stegmüller
*
* This file is a port of mhchemParser originally authored by Martin Hensel in javascript/typescript.
* The original license for this software can be found in the accompanying LICENSE.mhchemParser-ts.txt file.
*/
declare( strict_types = 1 );
namespace MediaWiki\Extension\Math\TexVC\Mhchem;
use MediaWiki\Extension\Math\TexVC\Mhchem\MhchemRegExp as Reg;
use RuntimeException;
/**
* Contains all matching regex patterns and match functions for mhchemParser in PHP.
*
* corresponds mostly to the 'patterns' array in line ~207 in mhchemParser.js by Martin Hensel
*
* @author Johannes Stegmüller
* @license GPL-2.0-or-later
*/
class MhchemPatterns {
/** @var array */
private array $patterns;
/**
* Matching patterns
* either regexes or function that return null or {match_:"a", remainder:"bc"}
* @return array
*/
public function getPatterns(): array {
return $this->patterns;
}
public function findObserveGroups( $input, $begExcl, $begIncl, $endIncl,
$endExcl = null, $beg2Excl = null, $beg2Incl = null,
$end2Incl = null, $end2Excl = null, $combine = null ): ?array {
$match = $this->matchObsGrInner( $input, $begExcl );
if ( $match === null ) {
return null;
}
$input = substr( $input, strlen( $match ) );
$match = $this->matchObsGrInner( $input, $begIncl );
if ( $match === null ) {
return null;
}
if ( $endIncl === "0" ) {
throw new RuntimeException( "error in condition, check next loc " );
}
$e = $this->findObserveGroupsInner( $input, strlen( $match ),
MhchemUtil::issetJS( $endIncl ) ? $endIncl : $endExcl );
if ( $e === null ) {
return null;
}
$match1 = substr( $input, 0, ( $endIncl ? $e["endMatchEnd"] : $e["endMatchBegin"] ) );
if ( !( MhchemUtil::issetJS( $beg2Excl ) || MhchemUtil::issetJS( $beg2Incl ) ) ) {
return [
"match_" => $match1,
"remainder" => substr( $input, $e["endMatchEnd"] )
];
} else {
$group2 = $this->findObserveGroups( substr( $input, $e["endMatchEnd"] ),
$beg2Excl, $beg2Incl, $end2Incl, $end2Excl );
if ( $group2 === null ) {
return null;
}
$matchRet = [ $match1, $group2["match_"] ];
return [
"match_" => ( $combine ? implode( "", $matchRet ) : $matchRet ),
"remainder" => $group2["remainder"]
];
}
}
private function matchObsGrInner( string $input, $pattern ) {
/**
* In javascript this is checking if the incoming pattern is a string,
* if not the assumption is that it is of regex type. Since PHP has
* strings here.
*/
if ( !$pattern instanceof Reg ) {
// Added this if to catch empty needle for strpos input in PHP
if ( !MhchemUtil::issetJS( $pattern ) ) {
return $pattern;
}
if ( strpos( $input, $pattern ) !== 0 ) {
return null;
}
return $pattern;
} else {
$matches = [];
$match = preg_match( $pattern->getRegExp(), $input, $matches );
if ( !$match ) {
return null;
}
return $matches[0];
}
}
private function findObserveGroupsInner( string $input, $i, $endChars ): ?array {
$braces = 0;
while ( $i < strlen( $input ) ) {
$a = $input[$i];
$match = $this->matchObsGrInner( substr( $input, $i ), $endChars );
if ( $match !== null && $braces === 0 ) {
return [ "endMatchBegin" => $i, "endMatchEnd" => $i + strlen( $match ) ];
} elseif ( $a === "{" ) {
$braces++;
} elseif ( $a === "}" ) {
if ( $braces === 0 ) {
// Unexpected character
throw new RuntimeException(
"ExtraCloseMissingOpen: Extra close brace or missing open brace" );
} else {
$braces--;
}
}
$i++;
}
return null;
}
public function __construct() {
$this->patterns = [
'empty' => new Reg( "/^$/" ),
'else' => new Reg( "/^./" ),
'else2' => new Reg( "/^./" ),
'space' => new Reg( "/^\s/" ),
'space A' => new Reg( "/^\s(?=[A-Z\\\\$])/" ),
'space$' => new Reg( "/^\s$/" ),
'a-z' => new Reg( "/^[a-z]/" ),
'x' => new Reg( "/^x/" ),
'x$' => new Reg( "/^x$/" ),
'i$' => new Reg( "/^i$/" ),
'letters' => new Reg(
"/^(?:[a-zA-Z\x{03B1}-\x{03C9}\x{0391}-\x{03A9}?@]|(?:\\\\(?: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]))))+/u" ),
'\\greek' => new Reg(
"/^\\\\(?: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]))/" ),
'one lowercase latin letter $' => new Reg( "/^(?:([a-z])(?:$|[^a-zA-Z]))$/" ),
'$one lowercase latin letter$ $' => new Reg( "/^\\\$(?:([a-z])(?:$|[^a-zA-Z]))\\\$$/" ),
'one lowercase greek letter $' => new Reg(
"/^(?:\\\$?[\x{003B1}-\x{0003C9}]\\\$?|\\\$?\\\\(?: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]))$/u" ),
'digits' => new Reg( "/^[0-9]+/" ),
'-9.,9' => new Reg( "/^[+\-]?(?:[0-9]+(?:[,.][0-9]+)?|[0-9]*(?:\.[0-9]+))/" ),
'-9.,9 no missing 0' => new Reg( "/^[+\-]?[0-9]+(?:[.,][0-9]+)?/" ),
'(-)(9.,9)(e)(99)' => static function ( $input ) {
$matches = [];
$match = preg_match( "/^(\+\-|\+\/\-|\+|\-|\\\\pm\s?)?([0-9]+(?:[,.][0-9]+)?|" .
"[0-9]*(?:\.[0-9]+))?(\((?:[0-9]+(?:[,.][0-9]+)?|[0-9]*(?:\.[0-9]+))\))?(?:(?:([eE])" .
"|\s*(\*|x|\\\\times|\x{00D7})\s*10\^)([+\-]?[0-9]+|\{[+\-]?[0-9]+\}))?/u", $input, $matches );
if ( $match && $matches[0] ) {
// could also match ""
return [ "match_" => array_slice( $matches, 1 ),
"remainder" => substr( $input, strlen( $matches[0] ) ) ];
}
return null;
},
'(-)(9)^(-9)' => new Reg( "/^(\+\-|\+\/\-|\+|\-|\\\\pm\s?)?([0-9]+(?:[,.][0-9]+)?|"
. "[0-9]*(?:\.[0-9]+)?)\^([+\-]?[0-9]+|\{[+\-]?[0-9]+\})/" ),
'state of aggregation $' => function ( $input ) {
// ... or crystal system
$a = $this->findObserveGroups( $input, "",
new Reg( "/^\([a-z]{1,3}(?=[\),])/" ), ")", "" );
if ( $a && preg_match( "/^($|[\s,;\)\]\}])/", $a["remainder"] ) ) {
return $a;
}
$matches = [];
$match = preg_match( "/^(?:\((?:\\\\ca\s?)?\\\$[amothc]\\\$\))/", $input, $matches );
if ( $match ) {
return [ "match_" => $matches[0], "remainder" => substr( $input, strlen( $matches[0] ) ) ];
}
return null;
},
'_{(state of aggregation)}$' => new Reg( "/^_\{(\([a-z]{1,3}\))\}/" ),
'{[(' => new Reg( "/^(?:\\\{|\[|\()/" ),
')]}' => new Reg( "/^(?:\)|\]|\\\})/" ),
', ' => new Reg( "/^[,;]\s*/" ),
',' => new Reg( "/^[,;]/" ),
'.' => new Reg( "/^[.]/" ),
'. __* ' => new Reg( "/^([.\x{22C5}\x{00B7}\x{2022}]|[*])\s*/u" ),
'...' => new Reg( "/^\.\.\.(?=$|[^.])/" ),
'^{(...)}' => function ( $input ) {
return $this->findObserveGroups( $input, "^{", "", "", "}" );
},
'^($...$)' => function ( $input ) {
return $this->findObserveGroups( $input, "^", "$", "$", "" );
},
'^a' => new Reg( "/^\^([0-9]+|[^\\\_])/u" ),
'^\\x{}{}' => function ( $input ) {
return $this->findObserveGroups( $input, "^",
new Reg( "/^\\\\[a-zA-Z]+\{/" ), "}", "", "",
"{", "}", "", true );
},
'^\\x{}' => function ( $input ) {
return $this->findObserveGroups( $input, "^",
new Reg( "/^\\\\[a-zA-Z]+\{/" ), "}", "" );
},
'^\\x' => new Reg( "/^\^(\\\\[a-zA-Z]+)\s*/" ),
'^(-1)' => new Reg( "/^\^(-?\d+)/" ),
'\'' => new Reg( "/^'/" ),
'_{(...)}' => function ( $input ) {
return $this->findObserveGroups( $input, "_{", "", "", "}" );
},
'_($...$)' => function ( $input ) {
return $this->findObserveGroups( $input, "_", "$", "$", "" );
},
'_9' => new Reg( "/^_([+\-]?[0-9]+|[^\\\\])/" ),
'_\\x{}{}' => function ( $input ) {
return $this->findObserveGroups( $input, "_", new Reg( "/^\\\\[a-zA-Z]+\{/" ), "}",
"", "", "{", "}", "", true );
},
'_\\x{}' => function ( $input ) {
return $this->findObserveGroups( $input, "_",
new Reg( "/^\\\\[a-zA-Z]+\{/" ), "}", "" );
},
'_\\x' => new Reg( "/^_(\\\\[a-zA-Z]+)\s*/" ),
'^_' => new Reg( "/^(?:\^(?=_)|\_(?=\^)|[\^_]$)/" ),
'{}^' => new Reg( "/^\{\}(?=\^)/" ),
'{}' => new Reg( "/^\{\}/" ),
'{...}' => function ( $input ) {
return $this->findObserveGroups( $input, "", "{", "}", "" );
},
'{(...)}' => function ( $input ) {
return $this->findObserveGroups( $input, "{", "", "", "}" );
},
'$...$' => function ( $input ) {
return $this->findObserveGroups( $input, "", "\$", "\$", "" );
},
'${(...)}$__$(...)$' => function ( $input ) {
return $this->findObserveGroups( $input, "\${", "", "", "}\$" )
?? $this->findObserveGroups( $input, "\$", "", "", "\$" );
},
'=<>' => new Reg( "/^[=<>]/" ),
'#' => new Reg( "/^[#\x{2261}]/u" ),
'+' => new Reg( "/^\+/" ),
// -space -, -; -] -/ -$ -state-of-aggregation orig: "/^-(?=[\s_},;\]/]|$|\([a-z]+\))/"
'-$' => new Reg( "/^-(?=[\s_},;\]\/]|$|\([a-z]+\))/u" ),
'-9' => new Reg( "/^-(?=[0-9])/" ),
'- orbital overlap' => new Reg( "/^-(?=(?:[spd]|sp)(?:$|[\s,;\)\]\}]))/" ),
'-' => new Reg( "/^-/" ),
'pm-operator' => new Reg( "/^(?:\\\\pm|\\\$\\\\pm\\\$|\+-|\+\/-)/" ),
'operator' => new Reg( "/^(?:\+|(?:[\-=<>]|<<|>>|\\\\approx|\\\$\\\\approx\\\$)(?=\s|$|-?[0-9]))/" ),
'arrowUpDown' => new Reg( "/^(?:v|\(v\)|\^|\(\^\))(?=$|[\s,;\)\]\}])/" ),
'\\bond{(...)}' => function ( $input ) {
return $this->findObserveGroups( $input, "\\bond{", "", "", "}" );
},
'->' => new Reg( '/^(?:<->|<-->|->|<-|<=>>|<<=>|<=>|[\x{2192}\x{27F6}\x{21CC}])/u' ),
'CMT' => new Reg( "/^[CMT](?=\[)/" ),
'[(...)]' => function ( $input ) { return $this->findObserveGroups( $input, "[", "",
"", "]" );
},
'1st-level escape' => new Reg( "/^(&|\\\\\\\\|\\\\hline)\s*/" ),
// \\x - but output no space before
'\\,' => new Reg( "/^(?:\\\\[,\ ;:])/" ),
'\\x{}{}' => function ( $input ) {
return $this->findObserveGroups( $input, "", new Reg( "/^\\\\[a-zA-Z]+\{/" ), "}",
"", "", "{", "}", "", true );
},
'\\x{}' => function ( $input ) {
return $this->findObserveGroups( $input, "", new Reg( "/^\\\\[a-zA-Z]+\{/" ), "}",
"" );
},
'\\ca' => new Reg( "/^\\\\ca(?:\s+|(?![a-zA-Z]))/" ),
'\\x' => new Reg( "/^(?:\\\\[a-zA-Z]+\s*|\\\\[_&{}%])/" ),
// only those with numbers in front, because the others will be formatted correctly anyway
'orbital' => new Reg( "/^(?:[0-9]{1,2}[spdfgh]|[0-9]{0,2}sp)(?=$|[^a-zA-Z])/" ),
'others' => new Reg( "/^[\/~|]/" ),
'\\frac{(...)}' => function ( $input ) {
return $this->findObserveGroups( $input, "\\frac{", "",
"", "}", "{", "", "", "}" );
},
'\\overset{(...)}' => function ( $input ) {
return $this->findObserveGroups( $input, "\\overset{", "",
"", "}", "{", "", "", "}" );
},
'\\underset{(...)}' => function ( $input ) {
return $this->findObserveGroups( $input, "\\underset{", "",
"", "}", "{", "", "", "}" );
},
'\\underbrace{(...)}' => function ( $input ) {
return $this->findObserveGroups( $input, "\\underbrace{", "",
"", "}_", "{", "", "", "}" );
},
'\\color{(...)}' => function ( $input ) {
return $this->findObserveGroups( $input, "\\color{", "", "", "}" );
},
'\\color{(...)}{(...)}' => function ( $input ) {
// ?? instead of ||
return $this->findObserveGroups( $input, "\\color{", "",
"", "}", "{", "", "", "}" ) ??
$this->findObserveGroups( $input, "\\color", "\\", "",
new Reg( "/^(?=\{)/" ), "{", "", "", "}" );
},
'\\ce{(...)}' => function ( $input ) {
return $this->findObserveGroups( $input, "\\ce{", "", "", "}" );
},
'\\pu{(...)}' => function ( $input ) { return $this->findObserveGroups( $input,
"\\pu{", "", "", "}" );
},
'oxidation$' => new Reg( "/^(?:[+-][IVX]+|(?:\\\\pm|\\\$\\\\pm\\\$|\+-|\+\/-)\s*0)$/" ),
'd-oxidation$' => new Reg( "/^(?:[+-]?[IVX]+|(?:\\\\pm|\\\$\\\\pm\\\$|\+-|\+\/-)\s*0)$/" ),
'1/2$' => new Reg( "/^[+\-]?(?:[0-9]+|\\\$[a-z]\\\$|[a-z])\/[0-9]+(?:\\\$[a-z]\\\$|[a-z])?$/" ),
'amount' => function ( $input ) {
$matches = [];
// e.g. 2, 0.5, 1/2, -2, n/2, +; $a$ could be added later in parsing
$match = preg_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))/", $input, $matches );
if ( $match ) {
return [ "match_" => $matches[0], "remainder" => substr( $input, strlen( $matches[0] ) ) ];
}
$a = $this->findObserveGroups( $input, "", "$", "$", "" );
// e.g. $2n-1$, $-$
if ( MhchemUtil::issetJS( $a ) ) {
$matchesI = [];
$match = preg_match( "/^\\\$(?:\(?[+\-]?(?:[0-9]*[a-z]?[+\-])" .
"?[0-9]*[a-z](?:[+\-][0-9]*[a-z]?)?\)?|\+|-)\\\$$/", $a["match_"] ?? "",
$matchesI );
if ( $match ) {
return [ "match_" => $matchesI[0], "remainder" => substr( $input, strlen( $matchesI[0] ) ) ];
}
}
return null;
},
'amount2' => function ( $input ) {
/* @phan-suppress-next-line PhanInfiniteRecursion, PhanUndeclaredInvokeInCallable */
return $this->patterns['amount']( $input );
},
'(KV letters),' => new Reg( "/^(?:[A-Z][a-z]{0,2}|i)(?=,)/" ),
'formula$' => static function ( $input ) {
if ( preg_match( "/^\([a-z]+\)$/", $input ) ) {
// state of aggregation = no formula
return null;
}
$matches = [];
$match = preg_match( "/^(?:[a-z]|(?:[0-9\ \+\-\,\.\(\)]+[a-z])+[0-9\ \+\-\,\.\(\)]*|"
. "(?:[a-z][0-9\ \+\-\,\.\(\)]+)+[a-z]?)$/", $input, $matches );
if ( $match ) {
return [ "match_" => $matches[0], "remainder" => substr( $input, strlen( $matches[0] ) ) ];
}
return null;
},
'uprightEntities' => new Reg( "/^(?:pH|pOH|pC|pK|iPr|iBu)(?=$|[^a-zA-Z])/" ),
'/' => new Reg( "/^\s*(\/)\s*/" ),
'//' => new Reg( "/^\s*(\/\/)\s*/" ),
'*' => new Reg( "/^\s*[*.]\s*/" )
];
}
/**
* Matching function
* e.g. match("a", input) will look for the regexp called "a" and see if it matches
* returns null or {match_:"a", remainder:"bc"}
* @param string $m key for fetching a pattern
* @param string $input string to check
* @return array|mixed|null information about the match
*/
public function match( string $m, string $input ) {
$pattern = $this->patterns[$m] ?? null;
if ( !$pattern ) {
// Trying to use non-existing pattern
throw new RuntimeException( "MhchemBugP: mhchem bug P. Please report. (" . $m . ")" );
} elseif ( $pattern instanceof Reg ) {
$matches = [];
$match = preg_match( $pattern->getRegExp(), $input, $matches );
if ( $match ) {
if ( count( $matches ) > 2 ) {
return [
"match_" => array_slice( $matches, 1 ),
"remainder" => substr( $input, strlen( $matches[0] ) )
];
} else {
return [
"match_" => MhchemUtil::issetJS( $matches[1] ?? null ) ? $matches[1] : $matches[0],
"remainder" => substr( $input, strlen( $matches[0] ) )
];
}
}
return null;
} elseif ( is_callable( $pattern ) ) {
// $pattern cannot be an instance of MhchemRegExp here, which causes this warning.
/* @phan-suppress-next-line PhanUndeclaredInvokeInCallable */
return $this->patterns[$m]( $input );
} else {
return null;
}
}
}

查看文件

@ -0,0 +1,33 @@
<?php
/**
* Copyright (c) 2023 Johannes Stegmüller
*
* This file is a port of mhchemParser originally authored by Martin Hensel in javascript/typescript.
* The original license for this software can be found in the accompanying LICENSE.mhchemParser-ts.txt file.
*/
namespace MediaWiki\Extension\Math\TexVC\Mhchem;
/**
* Wrapper class to declare a hardcoded string to a regular expression.
* @author Johannes Stegmüller
* @license GPL-2.0-or-later
*/
class MhchemRegExp {
/** @var string regular expression pattern as a string */
private string $regexp;
/**
* Utility class to distinguish Regular expression strings defined in
* the codebase of mhchemParser from regular strings.
* @param string $pattern regular expression pattern, usually of the format "/regexp/"
*/
public function __construct( string $pattern ) {
$this->regexp = $pattern;
}
public function getRegExp(): string {
return $this->regexp;
}
}

文件差异内容过多而无法显示 加载差异

查看文件

@ -0,0 +1,408 @@
<?php
/**
* Copyright (c) 2023 Johannes Stegmüller
*
* This file is a port of mhchemParser originally authored by Martin Hensel in javascript/typescript.
* The original license for this software can be found in the accompanying LICENSE.mhchemParser-ts.txt file.
*/
namespace MediaWiki\Extension\Math\TexVC\Mhchem;
use MediaWiki\Extension\Math\TexVC\MHChem\MhchemUtil as MU;
use RuntimeException;
/**
* Takes MhchemParser output and convert it to TeX
*
* Functionality is the same as mhchemTexify class at ~line 1505 in mhchemParser.js
* in mhchemParser by Martin Hensel.
*
* @author Johannes Stegmüller
* @license GPL-2.0-or-later
*/
class MhchemTexify {
/** @var bool optimize the output TeX for TexVC */
private bool $optimizeForTexVC;
/**
* Takes MhchemParser output and convert it to TeX
* @param bool $optimizeForTexVC optimizes the output for TexVC grammar by
* wrapping dimensions for some TeX commands in curly brackets.
*/
public function __construct( bool $optimizeForTexVC = false ) {
$this->optimizeForTexVC = $optimizeForTexVC;
}
public function go( $input, $addOuterBraces ): string {
if ( !MhchemUtil::issetJS( $input ) ) {
return "";
}
$res = "";
$cee = false;
for ( $i = 0; $i < count( $input ); $i++ ) {
$inputI = $input[$i];
if ( is_string( $inputI ) ) {
$res .= $inputI;
} else {
$res .= self::go2( $inputI );
if ( $inputI["type_"] === '1st-level escape' ) {
$cee = true;
}
}
}
if ( $addOuterBraces && !$cee && $res ) {
$res = "{" . $res . "}";
}
return $res;
}
private function goInner( $input ): string {
return self::go( $input, false );
}
private function strReplaceFirst( $search, $replace, $subject ): string {
return implode( $replace, explode( $search, $subject, 2 ) );
}
private function go2( $buf ): string {
switch ( $buf["type_"] ) {
case 'chemfive':
$res = "";
$b5 = [
"a" => self::goInner( $buf["a"] ),
"b" => self::goInner( $buf["b"] ),
"p" => self::goInner( $buf["p"] ),
"o" => self::goInner( $buf["o"] ),
"q" => self::goInner( $buf["q"] ),
"d" => self::goInner( $buf["d"] )
];
if ( MU::issetJS( $b5["a"] ) ) {
if ( preg_match( "/^[+\-]/", $b5["a"] ) ) {
$b5["a"] = "{" . $b5["a"] . "}";
}
$res .= $b5["a"] . "\\,";
}
if ( MU::issetJS( $b5["b"] ) || MU::issetJS( $b5["p"] ) ) {
$res .= "{\\vphantom{A}}";
$res .= "^{\\hphantom{" . ( $b5["b"] ) . "}}_{\\hphantom{" . ( $b5["p"] ) . "}}";
$res .= !$this->optimizeForTexVC ? "\\mkern-1.5mu" : "\\mkern{-1.5mu}";
$res .= "{\\vphantom{A}}";
$res .= "^{\\smash[t]{\\vphantom{2}}\\llap{" . ( $b5["b"] ) . "}}";
$res .= "_{\\vphantom{2}\\llap{\\smash[t]{" . ( $b5["p"] ) . "}}}";
}
if ( MU::issetJS( $b5["o"] ) ) {
if ( preg_match( "/^[+\-]/", $b5["o"] ) ) {
$b5["o"] = "{" . $b5["o"] . "}";
}
$res .= $b5["o"];
}
if ( isset( $buf["dType"] ) && $buf["dType"] === 'kv' ) {
if ( MU::issetJS( $b5["d"] ) || MU::issetJS( $b5["q"] ) ) {
$res .= "{\\vphantom{A}}";
}
if ( MU::issetJS( $b5["d"] ) ) {
$res .= "^{" . $b5["d"] . "}";
}
if ( MU::issetJS( $b5["q"] ) ) {
$res .= "_{\\smash[t]{" . $b5["q"] . "}}";
}
} elseif ( MU::issetJS( $buf["dType"] ?? null ) && $buf["dType"] === 'oxidation' ) {
if ( MU::issetJS( $b5["d"] ) ) {
$res .= "{\\vphantom{A}}";
$res .= "^{" . $b5["d"] . "}";
}
if ( MU::issetJS( $b5["q"] ) ) {
$res .= "{\\vphantom{A}}";
$res .= "_{\\smash[t]{" . $b5["q"] . "}}";
}
} else {
if ( MU::issetJS( $b5["q"] ) ) {
$res .= "{\\vphantom{A}}";
$res .= "_{\\smash[t]{" . $b5["q"] . "}}";
}
if ( MU::issetJS( $b5["d"] ) ) {
$res .= "{\\vphantom{A}}";
$res .= "^{" . $b5["d"] . "}";
}
}
break;
case 'roman numeral':
case 'rm':
$res = "\\mathrm{" . $buf["p1"] . "}";
break;
case 'text':
if ( preg_match( "/[\^_]/", $buf["p1"] ) ) {
$buf["p1"] = self::strReplaceFirst( "-", "\\text{-}",
self::strReplaceFirst( " ", "~", $buf["p1"] ) );
$res = "\\mathrm{" . $buf["p1"] . "}";
} else {
$res = "\\text{" . $buf["p1"] . "}";
}
break;
case 'state of aggregation':
$res = ( !$this->optimizeForTexVC ? "\\mskip2mu " : "\\mskip{2mu} " ) . self::goInner( $buf["p1"] );
break;
case 'state of aggregation subscript':
$res = ( !$this->optimizeForTexVC ? "\\mskip1mu " : "\\mskip{1mu} " ) . self::goInner( $buf["p1"] );
break;
case 'bond':
$res = self::getBond( $buf["kind_"] );
if ( !$res ) {
throw new RuntimeException( "MhchemErrorBond: mhchem Error. Unknown bond type ("
. $buf["kind_"] . ")" );
}
break;
case 'frac':
$c = "\\frac{" . $buf["p1"] . "}{" . $buf["p2"] . "}";
$res = "\\mathchoice{\\textstyle" . $c . "}{" . $c . "}{" . $c . "}{" . $c . "}";
break;
case 'pu-frac':
$d = "\\frac{" . self::goInner( $buf["p1"] ) . "}{" . self::goInner( $buf["p2"] ) . "}";
$res = "\\mathchoice{\\textstyle" . $d . "}{" . $d . "}{" . $d . "}{" . $d . "}";
break;
case '1st-level escape':
case 'tex-math':
$res = $buf["p1"] . " ";
break;
case 'frac-ce':
$res = "\\frac{" . self::goInner( $buf["p1"] ) . "}{" . self::goInner( $buf["p2"] ) . "}";
break;
case 'overset':
$res = "\\overset{" . self::goInner( $buf["p1"] ) . "}{" . self::goInner( $buf["p2"] ) . "}";
break;
case 'underset':
$res = "\\underset{" . self::goInner( $buf["p1"] ) . "}{" . self::goInner( $buf["p2"] ) . "}";
break;
case 'underbrace':
$res = "\\underbrace{" . self::goInner( $buf["p1"] ) . "}_{" . self::goInner( $buf["p2"] ) . "}";
break;
case 'color':
$res = "{\\color{" . $buf["color1"] . "}{" . self::goInner( $buf["color2"] ) . "}}";
break;
case 'color0':
$res = "\\color{" . $buf["color"] . "}";
break;
case 'arrow':
$b6 = [
"rd" => self::goInner( $buf["rd"] ),
"rq" => self::goInner( $buf["rq"] )
];
$arrow = self::getArrow( $buf["r"] );
if ( MU::issetJS( $b6["rd"] ) || MU::issetJS( $b6["rq"] ) ) {
if ( $buf["r"] === "<=>" || $buf["r"] === "<=>>" || $buf["r"] === "<<=>" || $buf["r"] === "<-->" ) {
$arrow = "\\long" . $arrow;
if ( MU::issetJS( $b6["rd"] ) ) {
$arrow = "\\overset{" . $b6["rd"] . "}{" . $arrow . "}";
}
if ( MU::issetJS( $b6["rq"] ) ) {
if ( $buf["r"] === "<-->" ) {
$arrow = !$this->optimizeForTexVC ?
"\\underset{\\lower2mu{" . $b6["rq"] . "}}{" . $arrow . "}"
: "\\underset{\\lower{2mu}{" . $b6["rq"] . "}}{" . $arrow . "}";
} else {
$arrow = !$this->optimizeForTexVC ?
"\\underset{\\lower6mu{" . $b6["rq"] . "}}{" . $arrow . "}"
: "\\underset{\\lower{6mu}{" . $b6["rq"] . "}}{" . $arrow . "}";
}
}
$arrow = " {}\\mathrel{" . $arrow . "}{} ";
} else {
if ( MU::issetJS( $b6["rq"] ) ) {
$arrow .= "[{" . $b6["rq"] . "}]";
}
$arrow .= "{" . $b6["rd"] . "}";
$arrow = " {}\\mathrel{\\x" . $arrow . "}{} ";
}
} else {
$arrow = " {}\\mathrel{\\long" . $arrow . "}{} ";
}
$res = $arrow;
break;
case 'operator':
$res = self::getOperator( $buf["kind_"] );
break;
default:
$res = null;
}
if ( isset( $res ) ) {
return $res;
}
switch ( $buf["type_"] ) {
case 'space':
$res = " ";
break;
case 'tinySkip':
$res = !$this->optimizeForTexVC ? '\\mkern2mu' : '\\mkern{2mu}';
break;
case 'pu-space-1':
case 'entitySkip':
$res = "~";
break;
case 'pu-space-2':
$res = !$this->optimizeForTexVC ? "\\mkern3mu " : "\\mkern{3mu} ";
break;
case '1000 separator':
$res = !$this->optimizeForTexVC ? "\\mkern2mu " : "\\mkern{2mu} ";
break;
case 'commaDecimal':
$res = "{,}";
break;
case 'comma enumeration L':
$res = "{" . $buf["p1"] . "}" . ( !$this->optimizeForTexVC ? "\\mkern6mu " : "\\mkern{6mu} " );
break;
case 'comma enumeration M':
$res = "{" . $buf["p1"] . "}" . ( !$this->optimizeForTexVC ? "\\mkern3mu " : "\\mkern{3mu} " );
break;
case 'comma enumeration S':
$res = "{" . $buf["p1"] . "}" . ( !$this->optimizeForTexVC ? "\\mkern1mu " : "\\mkern{1mu} " );
break;
case 'hyphen':
$res = "\\text{-}";
break;
case 'addition compound':
$res = "\\,{\\cdot}\\,";
break;
case 'electron dot':
$res = !$this->optimizeForTexVC ?
"\\mkern1mu \\bullet\\mkern1mu " : "\\mkern{1mu} \\bullet\\mkern{1mu} ";
break;
case 'KV x':
$res = "{\\times}";
break;
case 'prime':
$res = "\\prime ";
break;
case 'cdot':
$res = "\\cdot ";
break;
case 'tight cdot':
$res = !$this->optimizeForTexVC ? "\\mkern1mu{\\cdot}\\mkern1mu " : "\\mkern{1mu}{\\cdot}\\mkern{1mu} ";
break;
case 'times':
$res = "\\times ";
break;
case 'circa':
$res = "{\\sim}";
break;
case '^':
$res = "uparrow";
break;
case 'v':
$res = "downarrow";
break;
case 'ellipsis':
$res = "\\ldots ";
break;
case '/':
$res = "/";
break;
case ' / ':
$res = "\\,/\\,";
break;
default:
throw new RuntimeException( "MhchemBugT: mhchem bug T. Please report." );
}
return $res;
}
private function getArrow( $a ): string {
switch ( $a ) {
case "\u2192":
case "\u27F6":
case "->":
return "rightarrow";
case "<-":
return "leftarrow";
case "<->":
return "leftrightarrow";
case "<-->":
return "leftrightarrows";
case "\u21CC":
case "<=>":
return "rightleftharpoons";
case "<=>>":
return "Rightleftharpoons";
case "<<=>":
return "Leftrightharpoons";
default:
throw new RuntimeException( "MhchemBugT: mhchem bug T. Please report." );
}
}
private function getBond( $a ): string {
switch ( $a ) {
case "1":
case "-":
return "{-}";
case "2":
case "=":
return "{=}";
case "3":
case "#":
return "{\\equiv}";
case "~":
return "{\\tripledash}";
case "~-":
return !$this->optimizeForTexVC ? "{\\rlap{\\lower.1em{-}}\\raise.1em{\\tripledash}}"
: "{\\rlap{\\lower{.1em}{-}}\\raise{.1em}{\\tripledash}}";
case "~--":
case "~=":
return !$this->optimizeForTexVC ? "{\\rlap{\\lower.2em{-}}\\rlap{\\raise.2em{\\tripledash}}-}"
: "{\\rlap{\\lower{.2em}{-}}\\rlap{\\raise{.2em}{\\tripledash}}-}";
case "-~-":
return !$this->optimizeForTexVC ? "{\\rlap{\\lower.2em{-}}\\rlap{\\raise.2em{-}}\\tripledash}"
: "{\\rlap{\\lower{.2em}{-}}\\rlap{\\raise{.2em}{-}}\\tripledash}";
case "...":
return "{{\\cdot}{\\cdot}{\\cdot}}";
case "....":
return "{{\\cdot}{\\cdot}{\\cdot}{\\cdot}}";
case "->":
return "{\\rightarrow}";
case "<-":
return "{\\leftarrow}";
case "<":
return "{<}";
case ">":
return "{>}";
default:
throw new RuntimeException( "MhchemBugT: mhchem bug T. Please report." );
}
}
private function getOperator( $a ): string {
switch ( $a ) {
case "+":
return " {}+{} ";
case "-":
return " {}-{} ";
case "=":
return " {}={} ";
case "<":
return " {}<{} ";
case ">":
return " {}>{} ";
case "<<":
return " {}\\ll{} ";
case ">>":
return " {}\\gg{} ";
case "\\pm":
return " {}\\pm{} ";
case "$\\approx$":
case "\\approx":
return " {}\\approx{} ";
case "(v)":
case "v":
return " \\downarrow{} ";
case "(^)":
case "^":
return " \\uparrow{} ";
default:
throw new RuntimeException( "MhchemBugT: mhchem bug T. Please report." );
}
}
}

查看文件

@ -0,0 +1,79 @@
<?php
/**
* Copyright (c) 2023 Johannes Stegmüller
*
* This file is a port of mhchemParser originally authored by Martin Hensel in javascript/typescript.
* The original license for this software can be found in the accompanying LICENSE.mhchemParser-ts.txt file.
*/
namespace MediaWiki\Extension\Math\TexVC\Mhchem;
/**
* Some utility classes mostly for creating similar functionalities
* like in javascript in PHP.
*
* concatArray method here has the same functionality as concatArray (~l.194)
* in mhchemParser.js by Martin Hensel.
*
* @author Johannes Stegmüller
* @license GPL-2.0-or-later
*/
class MhchemUtil {
/**
* The input is used as boolean operator in a javascript-type if condition,
* example: "if(input)"
* output has the same boolean results as an if-condition in javascript.
* arrays as input have to be used like this "issetJS($arr["b"] ?? null);"
* properties as input have to be used like this "issetJS($inst->prop ?? null);"
* @param mixed|null $input input to be checked in a javascript-type if condition
* @return bool indicator if input is populated
*/
public static function issetJS( $input ): bool {
if ( $input === 0 || $input == "" ) {
return false;
}
return true;
}
/**
* Checks if the incoming string is containing a regex pattern.
* @param string $input string to verify
* @param string $subject subject to check, usually empty string
* @return bool true if regex pattern, false if not
*/
public static function isRegex( string $input, string $subject = "" ): bool {
/**
* Ignoring preg_match phpcs error here, since this is the fastest variant: 582 ms for MMLmhchemTestLocal,
* 835 ms for the try catch version of this, 735 ms for the version deactivating error handler
* during function call.
* See: https://stackoverflow.com/questions/16039362/how-can-i-suppress-phpcs-warnings-using-comments
*/
// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
return !( @preg_match( $input, $subject ) === false );
}
/**
* Checks if an array is an associative array
* @param array $array to check
* @return bool true if associative, otherwise false
*/
public static function isAssoc( array $array ): bool {
return ( $array !== array_values( $array ) );
}
public static function concatArray( &$a, $b ) {
if ( self::issetJS( $b ) ) {
if ( is_array( $b ) && ( self::isAssoc( $b ) ) ) {
$a[] = $b;
} elseif ( is_array( $b ) && !self::isAssoc( $b ) ) {
foreach ( $b as $value ) {
$a[] = $value;
}
} else {
$a[] = $b;
}
}
}
}

查看文件

@ -0,0 +1,106 @@
<?php
namespace MediaWiki\Extension\Math\TexVC\Mhchem;
use Exception;
use MediaWiki\Extension\Math\TexVC\MMLmappings\Util\MMLTestUtil;
use MediaWiki\Extension\Math\TexVC\MMLmappings\Util\MMLTestUtilHTML;
use MediaWiki\Extension\Math\TexVC\TexVC;
use MediaWikiUnitTestCase;
/**
* This test checks the functionality MHCHem module within MediaWiki environment
* It is running all defined testcases from "Mhchemv4mml.json" these are from test.html from javascript mhchemparser.
* Mhchemv4mml.json can be recreated by running the maintenance script JsonToMathML with Mhchemv4tex.json
* within a MediaWiki environment:
* 'php extensions/Math/maintenance/JsonToMathML.php
* /var/www/html/extensions/Math/tests/phpunit/unit/TexVC/Mhchem/Mhchemv4tex.json
* /var/www/html/extensions/Math/tests/phpunit/unit/TexVC/Mhchem/Mhchemv4mml.json -i 3'
*
* Settings for running the testbench are defined as constants within this class.
*
* @covers \MediaWiki\Extension\Math\TexVC\TexVC
*/
final class MMLmhchemTest extends MediaWikiUnitTestCase {
private static bool $LOGMHCHEM = false;
private static bool $SKIPXMLVALIDATION = false;
private static string $FILENAMEREF = __DIR__ . "/Mhchemv4mml.json";
private static bool $APPLYFILTER = false;
private static int $FILTERSTART = 93;
private static int $FILTERLENGTH = 1;
private static bool $GENERATEHTML = false;
private static string $GENERATEDHTMLFILE = __DIR__ . "/MMLmhchemTest-Output.html";
private static array $SKIPPEDINDICES = [];
public static function setUpBeforeClass(): void {
MMLTestUtilHTML::generateHTMLstart( self::$GENERATEDHTMLFILE, [ "name","TeX-Input",
"Tex-MhchemParser","Tex-PHP-Mhchem" ], self::$GENERATEHTML );
}
public static function tearDownAfterClass(): void {
MMLTestUtilHTML::generateHTMLEnd( self::$GENERATEDHTMLFILE, self::$GENERATEHTML );
}
/**
* @dataProvider provideTestCases
* @throws Exception
*/
public function testTexVC( $title, $tc ) {
$texVC = new TexVC();
if ( in_array( $tc->ctr, self::$SKIPPEDINDICES ) ) {
MMLTestUtilHTML::generateHTMLtableRow( self::$GENERATEDHTMLFILE, [ $tc->ctr, $tc->tex,
"skipped", "skipped", "skipped" ], false, self::$GENERATEHTML );
$this->assertTrue( true );
return;
}
# Fetch result from TexVC(PHP)
$texVC->check( $tc->tex, [
'debug' => false,
'usemathrm' => false,
'oldtexvc' => false,
'usemhchem' => true
] );
$mhchemParser = new MhchemParser( self::$LOGMHCHEM );
$mhchemOutput = $mhchemParser->toTex( $tc->tex, $tc->typeC );
MMLTestUtilHTML::generateHTMLtableRow(
self::$GENERATEDHTMLFILE, [ $title, $tc->tex, $tc->texNew, $mhchemOutput ],
false, self::$GENERATEHTML );
if ( !self::$SKIPXMLVALIDATION ) {
$this->assertEquals( $tc->texNew, $mhchemOutput );
} else {
$this->assertTrue( true );
}
}
public static function provideTestCases() {
$fileTestcases = MMLTestUtil::getJSON( self::$FILENAMEREF );
$f = [];
// Adding running indices for location of tests.
$ctr = 0;
foreach ( $fileTestcases as $tcF ) {
$tc = [
"ctr" => $ctr,
"tex" => $tcF->tex,
"texNew" => $tcF->texNew,
"type" => $tcF->type,
"typeC" => $tcF->typeC,
"mml_mathoid" => $tcF->mmlMathoid,
"mml_latexml" => $tcF->mmlLaTeXML,
];
$f[] = [ "tc#" . str_pad( $ctr, 3, '0', STR_PAD_LEFT ) . " " . $tcF->description,
(object)$tc ];
$ctr++;
}
// Filtering results by index if necessary
if ( self::$APPLYFILTER ) {
$f = array_slice( $f, self::$FILTERSTART, self::$FILTERLENGTH );
}
return $f;
}
}

查看文件

@ -0,0 +1,385 @@
<?php
namespace MediaWiki\Extension\Math\TexVC\Mhchem;
use MediaWikiUnitTestCase;
/**
* Some simple tests to test specific functions within
* MhchemParser in PHP.
*
* @covers \MediaWiki\Extension\Math\TexVC\TexVC
*/
final class MhchemBasicTest extends MediaWikiUnitTestCase {
public function testZk() {
// ReplaceFirst in MhchemTexify is introduced with this test, is this meant to be replace in javascript ?
$input = "\ce{{Zk_{c} e^2 m_{e}}}";
$mhchemParser = new MhchemParser();
$out = $mhchemParser->toTex( $input, "ce" );
$this->assertEquals( "{{\mathrm{Zk_{c}~e^2 m_{e}}}}", $out );
}
public function testTextPattern() {
// With this test a fix for the a^2 pattern to ^\\\_ instead ^\\x was introduced
$input = "K^\\text{b, 2}";
$mhchemParser = new MhchemParser();
$out = $mhchemParser->toTex( $input, "ce" );
$this->assertEquals( "{\mathrm{K}{\\vphantom{A}}^{\\text{b, 2}}}", $out );
}
public function testFormulaDollarPattern() {
$input = "\ce{H_{n + 2}}";
$mhchemParser = new MhchemParser();
$out = $mhchemParser->toTex( $input, "ce" );
$this->assertEquals( "{\mathrm{H}{\\vphantom{A}}_{\smash[t]{n + 2}}}", $out );
}
public function testPhantom() {
$input = "\ce{-C-O-}";
$mhchemParser = new MhchemParser();
$out = $mhchemParser->toTex( $input, "ce" );
$this->assertEquals( "{{-}\mathrm{C}{-}\mathrm{O}{\\vphantom{A}}^{-}}", $out );
}
public function testH20withPU() {
$input = "H2O (\pu{1g})";
$mhchemParser = new MhchemParser();
$out = $mhchemParser->toTex( $input, "ce" );
$this->assertEquals( "{\mathrm{H}{\\vphantom{A}}_{\smash[t]{2}}\mathrm{O}~({1~\mathrm{g}})}", $out );
}
public function testPUt1010() {
$input = "10^-10 m";
$mhchemParser = new MhchemParser();
$out = $mhchemParser->toTex( $input, "pu" );
$this->assertEquals( "{10^{-10}~\mathrm{m}}", $out );
}
public function testPUtimes() {
$input = "7.8 \\times 10^-10 m";
$mhchemParser = new MhchemParser();
$out = $mhchemParser->toTex( $input, "pu" );
$this->assertEquals( "{7.8\\times 10^{-10}~\mathrm{m}}", $out );
}
public function testPUE10() {
$input = "E10";
$mhchemParser = new MhchemParser();
$out = $mhchemParser->toTex( $input, "pu" );
$this->assertEquals( "{10^{10}}", $out );
}
public function testXParenthesis() {
$input = "\${x}$";
$mhchemParser = new MhchemParser();
$out = $mhchemParser->toTex( $input, "ce" );
$this->assertEquals( "{x }", $out );
}
public function testH2OAmount() {
$input = "\$n$/2 H2O";
$mhchemParser = new MhchemParser();
$out = $mhchemParser->toTex( $input, "ce" );
$this->assertEquals(
"{\mathchoice{\\textstyle\\frac{n}{2}}{\\frac{n}{2}}{\\frac{n}{2}}{\\frac{n}{2}}\,\mathrm{H}" .
"{\\vphantom{A}}_{\smash[t]{2}}\mathrm{O}}", $out );
}
public function testH1() {
$input = "H^°";
$mhchemParser = new MhchemParser();
$out = $mhchemParser->toTex( $input, "ce" );
$this->assertEquals( "{\mathrm{H}{\\vphantom{A}}^{°}}", $out );
}
public function testDollar1() {
$input = "A $\pm$ B";
$mhchemParser = new MhchemParser();
$out = $mhchemParser->toTex( $input, "ce" );
$this->assertEquals( "{\mathrm{A} {}\pm{} \mathrm{B}}", $out );
}
public function testDollar2() {
$input = "1/2\$n\$ H2O";
$mhchemParser = new MhchemParser();
$out = $mhchemParser->toTex( $input, "ce" );
$this->assertEquals(
"{\mathchoice{\\textstyle\\frac{1}{2}}{\\frac{1}{2}}{\\frac{1}{2}}" .
"{\\frac{1}{2}}n \,\mathrm{H}{\\vphantom{A}}_{\smash[t]{2}}\\mathrm{O}}", $out );
}
public function testApattern() {
$input = "^0_-1n-";
$pattern = "/^\^([0-9]+|[^\\_])/";
$matches = [];
$match = preg_match( $pattern, $input, $matches );
$mhchemPatterns = new MhchemPatterns();
$matchesR = $mhchemPatterns->match( "^a", $input );
$this->assertSame( 1, $match );
$this->assertSame( "0", $matchesR["match_"] );
$this->assertEquals( "_-1n-", $matchesR["remainder"] );
}
public function testLettersPattern() {
$input = "mu-Cl";
$pattern = '/^(?:[a-zA-Z\x{03B1}-\x{03C9}\x{0391}-\x{03A9}?@]|(?:\\\\(?: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]))))+/u';
$matches = [];
$match = preg_match( $pattern, $input, $matches );
$this->assertTrue( $match == 1 );
}
public function testStateOfAggregationPattern() {
$input = "(\\ca\$c\$)";
$match = preg_match( '/^(?:\((?:\\\\ca\s?)?\$[amothc]\$\))/', $input );
$this->assertTrue( $match == 1 );
}
public function testCelsiusPattern() {
$input = "°C";
$output = preg_replace( "/\x{00B0}C|\^oC|\^{o}C/u", "{}^{\\circ}C", $input );
$target = "{}^{\circ}C";
$this->assertEquals( $target, $output );
}
public function testPatternsMatchObsGroups() {
$mhchemPatterns = new MhchemPatterns();
$a = $mhchemPatterns->findObserveGroups( "(aq)", "",
new MhchemRegExp( '/^\\([a-z]{1,3}(?=[\\),])/' ), ")", "" );
$target = [
"match_" => "(aq)",
"remainder" => ""
];
$this->assertEquals( $target, $a );
}
public function testIssetJS() {
$this->assertFalse( MhchemUtil::issetJS( "" ) );
$this->assertFalse( MhchemUtil::issetJS( null ) );
$this->assertFalse( MhchemUtil::issetJS( false ) );
$this->assertFalse( MhchemUtil::issetJS( 0 ) );
// checkEmpty(new Object("")); tbd
$this->assertTrue( MhchemUtil::issetJS( new \stdClass( "" ) ) );
$this->assertTrue( MhchemUtil::issetJS( "abc" ) );
$this->assertTrue( MhchemUtil::issetJS( "false" ) );
$this->assertTrue( MhchemUtil::issetJS( [] ) );
$this->assertTrue( MhchemUtil::issetJS( "0" ) );
$this->assertTrue( MhchemUtil::issetJS( 123 ) );
// Also the function should not crash when checking nested non-existent properties but return false
$test1 = [ "b" => 123 ];
$this->assertFalse( MhchemUtil::issetJS( $test1["a"] ?? null ) );
$this->assertTrue( MhchemUtil::issetJS( $test1["b"] ) );
}
public function testTransitionsInitTex() {
$empty = [
"pattern" => "empty",
"task" => [
"action_" => [
[ "type_" => "copy" ]
],
"stateArray" => [
"0"
]
],
];
$ce = [
"pattern" => "\\ce{(...)}",
"task" => [
"action_" => [
[ "type_" => "write", "option" => "{" ],
[ "type_" => "ce" ],
[ "type_" => "write", "option" => "}" ],
],
"stateArray" => [
"0"
]
],
];
$mhchemParser = new MhchemParser();
$mhchemStateMachines = new MhchemStateMachines( $mhchemParser );
$transitions = $mhchemStateMachines->stateMachines["tex"]["transitions"];
// When done eval(count($transition), NUMBER)
$emptyGen = $transitions[0][0];
$ceGen = $transitions[0][1];
$this->assertEquals( $empty, $emptyGen );
$this->assertEquals( $ce, $ceGen );
}
public function testTransitionsInitCe() {
$mhchemParser = new MhchemParser();
$mhchemStateMachines = new MhchemStateMachines( $mhchemParser );
$transitions = $mhchemStateMachines->stateMachines["ce"]["transitions"];
// When done eval(count($transition), NUMBER)
$this->assertCount( 23, $transitions );
$this->assertCount( 61, $transitions["0"] );
$this->assertCount( 50, $transitions["qD"] );
}
public function testMHPatternsMatch() {
$mhchemPatterns = new MhchemPatterns();
$pattern = "\ce{(...)}";
$matches = $mhchemPatterns->match( $pattern, "\ce{CO2 + C -> 2 CO}" );
$this->assertEquals( "CO2 + C -> 2 CO", $matches["match_"] );
$this->assertSame( "", $matches["remainder"] );
}
public function testMHPatternsMatch2() {
$mhchemPatterns = new MhchemPatterns();
$matches = $mhchemPatterns->match( 'letters', "C" );
$this->assertNotNull( $matches );
}
public function testMMHPatternsMatch3() {
$mhchemPatterns = new MhchemPatterns();
$matches2 = $mhchemPatterns->match( '->', "C" );
$this->assertNull( $matches2 );
}
public function testMHChemParserGo1() {
$target = [
"type_" => "chemfive",
"a" => [],
"b" => [],
"p" => [],
"o" => [
[
"type_" => "rm",
"p1" => "C"
]
],
"q" => [],
"d" => []
];
$mhchemParser = new MhchemParser();
$output = $mhchemParser->go( "C", "ce" );
$this->assertEquals( $target, $output[0] );
}
public function testMHChemParserGo2() {
$target = [
"type_" => "chemfive",
"a" => [],
"b" => [],
"p" => [],
"o" => [
[
"type_" => "rm",
"p1" => "CO"
]
],
"q" => [
"2"
],
"d" => []
];
$mhchemParser = new MhchemParser();
$output = $mhchemParser->go( "CO2", "ce" );
$this->assertEquals( $target, $output[0] );
}
public function testMHChemParserGo3() {
$target = [
[
"type_" => "arrow",
"r" => "->",
"rd" => [],
"rq" => []
]
];
$mhchemParser = new MhchemParser();
$output = $mhchemParser->go( "->", "ce" );
$this->assertEquals( $target, $output );
}
public function testMHChemParserGo4() {
$target = [
[
"type_" => "chemfive",
"a" => [],
"b" => [],
"p" => [],
"o" => [
"+"
],
"q" => [],
"d" => []
]
];
$mhchemParser = new MhchemParser();
$output = $mhchemParser->go( "+", "ce" );
$this->assertEquals( $target, $output );
}
public function testMHChemParserGo5() {
// order of patterns correct, hit different pattern
$mhchemParser = new MhchemParser();
$output = $mhchemParser->go( "CO2 + ", "ce" );
$this->assertCount( 2, $output );
$this->assertEquals( "chemfive", $output[0]["type_"] );
$this->assertEquals( "operator", $output[1]["type_"] );
}
public function testMHChemParserGo6() {
$mhchemParser = new MhchemParser();
$output = $mhchemParser->go( "CO2 + C -> 2 CO", "ce" );
$this->assertCount( 5, $output );
$this->assertEquals( "chemfive", $output[0]["type_"] );
$this->assertEquals( "operator", $output[1]["type_"] );
$this->assertEquals( "chemfive", $output[2]["type_"] );
$this->assertEquals( "arrow", $output[3]["type_"] );
$this->assertEquals( 2, $output[4]["a"][0] );
$this->assertEquals( "CO", $output[4]["o"][0]["p1"] );
}
public function testMHChemTexify() {
$mhchemParser = new MhchemParser();
$output = $mhchemParser->toTex( "CO2", "ce" );
$this->assertEquals( "{\\mathrm{CO}{\\vphantom{A}}_{\\smash[t]{2}}}", $output );
}
public function testConcatArray1() {
$a = [];
$b = [ "{" ];
$target = [ "{" ];
MhchemUtil::concatArray( $a, $b );
$this->assertEquals( $target, $a );
}
public function testConcatArray2() {
$a = [];
// This is an object in javascript typescript, in PHP this is currently an array.
$b = [ "type_" => "rm", "p1" => "C" ];
$target = [ $b ];
MhchemUtil::concatArray( $a, $b );
$this->assertEquals( $target, $a );
}
public function testConcatArray3() {
$a = [ "{" ];
$b = [ "type_" => "chemfive" ];
$target = [ $a[0], $b ];
MhchemUtil::concatArray( $a, $b );
$this->assertEquals( $target, $a );
}
}

文件差异因一行或多行过长而隐藏

查看文件

@ -0,0 +1,635 @@
{
"Init": [
{
"tex": "m_{\\ce{H2O}} = \\pu{1.2kg}",
"texNew": "m_{{\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}} = {1.2~\\mathrm{kg}}",
"type": "tex"
}
],
"Chemical Equations": [
{
"tex": "CO2 + C -> 2 CO",
"texNew": "{\\mathrm{CO}{\\vphantom{A}}_{\\smash[t]{2}} {}+{} \\mathrm{C} {}\\mathrel{\\longrightarrow}{} 2\\,\\mathrm{CO}}",
"type": "ce"
},
{
"tex": "Hg^2+ ->[I-] HgI2 ->[I-] [Hg^{II}I4]^2-",
"texNew": "{\\mathrm{Hg}{\\vphantom{A}}^{2+} {}\\mathrel{\\xrightarrow{\\mathrm{I}{\\vphantom{A}}^{-}}}{} \\mathrm{HgI}{\\vphantom{A}}_{\\smash[t]{2}} {}\\mathrel{\\xrightarrow{\\mathrm{I}{\\vphantom{A}}^{-}}}{} [\\mathrm{Hg}{\\vphantom{A}}^{\\mathrm{II}}\\mathrm{I}{\\vphantom{A}}_{\\smash[t]{4}}]{\\vphantom{A}}^{2-}}",
"type": "ce"
}
],
"Chemical Formulae": [
{
"tex": "H2O",
"texNew": "{\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}",
"type": "ce"
},
{
"tex": "Sb2O3",
"texNew": "{\\mathrm{Sb}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}{\\vphantom{A}}_{\\smash[t]{3}}}",
"type": "ce"
}
],
"Charges": [
{
"tex": "H+",
"texNew": "{\\mathrm{H}{\\vphantom{A}}^{+}}",
"type": "ce"
},
{
"tex": "CrO4^2-",
"texNew": "{\\mathrm{CrO}{\\vphantom{A}}_{\\smash[t]{4}}{\\vphantom{A}}^{2-}}",
"type": "ce"
},
{
"tex": "[AgCl2]-",
"texNew": "{[\\mathrm{AgCl}{\\vphantom{A}}_{\\smash[t]{2}}]{\\vphantom{A}}^{-}}",
"type": "ce"
},
{
"tex": "Y^99+",
"texNew": "{\\mathrm{Y}{\\vphantom{A}}^{99+}}",
"type": "ce"
},
{
"tex": "Y^{99+}",
"texNew": "{\\mathrm{Y}{\\vphantom{A}}^{99+}}",
"type": "ce"
}
],
"Stoichiometric Numbers": [
{
"tex": "2 H2O",
"texNew": "{2\\,\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}",
"type": "ce"
},
{
"tex": "2H2O",
"texNew": "{2\\,\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}",
"type": "ce"
},
{
"tex": "0.5 H2O",
"texNew": "{0.5\\,\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}",
"type": "ce"
},
{
"tex": "1/2 H2O",
"texNew": "{\\mathchoice{\\textstyle\\frac{1}{2}}{\\frac{1}{2}}{\\frac{1}{2}}{\\frac{1}{2}}\\,\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}",
"type": "ce"
},
{
"tex": "(1/2) H2O",
"texNew": "{(1/2)\\,\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}",
"type": "ce"
},
{
"tex": "$n$ H2O",
"texNew": "{n \\,\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}",
"type": "ce"
},
{
"tex": "n H2O",
"texNew": "{n\\,\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}",
"type": "ce"
},
{
"tex": "nH2O",
"texNew": "{n\\,\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}",
"type": "ce"
},
{
"tex": "n/2 H2O",
"texNew": "{\\mathchoice{\\textstyle\\frac{n}{2}}{\\frac{n}{2}}{\\frac{n}{2}}{\\frac{n}{2}}\\,\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}",
"type": "ce"
}
],
"Nuclides, Isotopes": [
{
"tex": "^{227}_{90}Th+",
"texNew": "{{\\vphantom{A}}^{\\hphantom{227}}_{\\hphantom{90}}\\mkern-1.5mu{\\vphantom{A}}^{\\smash[t]{\\vphantom{2}}\\llap{227}}_{\\vphantom{2}\\llap{\\smash[t]{90}}}\\mathrm{Th}{\\vphantom{A}}^{+}}",
"type":"ce"
},
{
"tex": "^{0}_{-1}n^{-}",
"texNew": "{{\\vphantom{A}}^{\\hphantom{0}}_{\\hphantom{-1}}\\mkern-1.5mu{\\vphantom{A}}^{\\smash[t]{\\vphantom{2}}\\llap{0}}_{\\vphantom{2}\\llap{\\smash[t]{-1}}}\\mathrm{n}{\\vphantom{A}}^{-}}",
"type":"ce"
},
{
"tex": "^0_-1n-",
"texNew": "{{\\vphantom{A}}^{\\hphantom{0}}_{\\hphantom{-1}}\\mkern-1.5mu{\\vphantom{A}}^{\\smash[t]{\\vphantom{2}}\\llap{0}}_{\\vphantom{2}\\llap{\\smash[t]{-1}}}\\mathrm{n}{\\vphantom{A}}^{-}}",
"type":"ce"
},
{
"tex": "H{}^3HO",
"texNew": "{\\mathrm{H}\\mkern2mu{\\vphantom{A}}^{\\hphantom{3}}_{\\hphantom{}}\\mkern-1.5mu{\\vphantom{A}}^{\\smash[t]{\\vphantom{2}}\\llap{3}}_{\\vphantom{2}\\llap{\\smash[t]{}}}\\mathrm{HO}}",
"type":"ce"
},
{
"tex": "H^3HO",
"texNew": "{\\mathrm{H}\\mkern2mu{\\vphantom{A}}^{\\hphantom{3}}_{\\hphantom{}}\\mkern-1.5mu{\\vphantom{A}}^{\\smash[t]{\\vphantom{2}}\\llap{3}}_{\\vphantom{2}\\llap{\\smash[t]{}}}\\mathrm{HO}}",
"type": "ce"
}
],
"Reaction Arrows": [
{
"tex": "A -> B",
"texNew": "{\\mathrm{A} {}\\mathrel{\\longrightarrow}{} \\mathrm{B}}",
"type":"ce"
},
{
"tex": "A <- B",
"texNew": "{\\mathrm{A} {}\\mathrel{\\longleftarrow}{} \\mathrm{B}}",
"type":"ce"
},
{
"tex": "A <-> B",
"texNew": "{\\mathrm{A} {}\\mathrel{\\longleftrightarrow}{} \\mathrm{B}}",
"type":"ce"
},
{
"tex": "A <--> B",
"texNew": "{\\mathrm{A} {}\\mathrel{\\longleftrightarrows}{} \\mathrm{B}}",
"type":"ce"
},
{
"tex": "A <=> B",
"texNew": "{\\mathrm{A} {}\\mathrel{\\longrightleftharpoons}{} \\mathrm{B}}",
"type":"ce"
},
{
"tex": "A <=>> B",
"texNew": "{\\mathrm{A} {}\\mathrel{\\longRightleftharpoons}{} \\mathrm{B}}",
"type":"ce"
},
{
"tex": "A <<=> B",
"texNew": "{\\mathrm{A} {}\\mathrel{\\longLeftrightharpoons}{} \\mathrm{B}}",
"type":"ce"
},
{
"tex": "A ->[H2O] B",
"texNew": "{\\mathrm{A} {}\\mathrel{\\xrightarrow{\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}}{} \\mathrm{B}}",
"type":"ce"
},
{
"tex": "A ->[{text above}][{text below}] B",
"texNew": "{\\mathrm{A} {}\\mathrel{\\xrightarrow[{{\\text{text below}}}]{{\\text{text above}}}}{} \\mathrm{B}}",
"type":"ce"
},
{
"tex": "A ->[$x$][$x_i$] B",
"texNew": "{\\mathrm{A} {}\\mathrel{\\xrightarrow[{x_i }]{x }}{} \\mathrm{B}}",
"type": "ce"
}
],
"Parentheses, Brackets, Braces": [
{
"tex": "(NH4)2S",
"texNew": "{(\\mathrm{NH}{\\vphantom{A}}_{\\smash[t]{4}}){\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{S}}",
"type":"ce"
},
{
"tex": "[\\{(X2)3\\}2]^3+",
"texNew": "{[\\{(\\mathrm{X}{\\vphantom{A}}_{\\smash[t]{2}}){\\vphantom{A}}_{\\smash[t]{3}}\\}{\\vphantom{A}}_{\\smash[t]{2}}]{\\vphantom{A}}^{3+}}",
"type":"ce"
},
{
"tex": "CH4 + 2 $\\left( \\ce{O2 + 79/21 N2} \\right)$",
"texNew": "{\\mathrm{CH}{\\vphantom{A}}_{\\smash[t]{4}} {}+{} 2\\,\\left( \\mathrm{O}{\\vphantom{A}}_{\\smash[t]{2}} {}+{} \\mathchoice{\\textstyle\\frac{79}{21}}{\\frac{79}{21}}{\\frac{79}{21}}{\\frac{79}{21}}\\,\\mathrm{N}{\\vphantom{A}}_{\\smash[t]{2}} \\right) }",
"type": "ce"
}
],
"States of Aggregation": [
{
"tex": "H2(aq)",
"texNew": "{\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mskip2mu (\\mathrm{aq})}",
"type":"ce"
},
{
"tex": "CO3^2-_{(aq)}",
"texNew": "{\\mathrm{CO}{\\vphantom{A}}_{\\smash[t]{3}}{\\vphantom{A}}^{2-}{\\vphantom{A}}_{\\smash[t]{\\mskip1mu (\\mathrm{aq})}}}",
"type":"ce"
},
{
"tex": "CO3^2-{}_{(aq)}",
"texNew": "{\\mathrm{CO}{\\vphantom{A}}_{\\smash[t]{3}}{\\vphantom{A}}^{2-}{\\vphantom{A}}_{\\smash[t]{\\mskip1mu (\\mathrm{aq})}}}",
"type":"ce"
},
{
"tex": "NaOH(aq,$\\infty$)",
"texNew": "{\\mathrm{NaOH}\\mskip2mu (\\mathrm{aq},\\infty )}",
"type": "ce"
}
],
"Crystal Systems": [
{
"tex": "ZnS ($c$)",
"texNew": "{\\mathrm{ZnS}\\mskip2mu (c )}",
"type":"ce"
},
{
"tex": "ZnS (\\ca$c$)",
"texNew": "{\\mathrm{ZnS}\\mskip2mu ({\\sim}c )}",
"type": "ce"
}
],
"Variables like __*x*, *n*, 2*n*+1__": [
{
"tex": "NO_x",
"texNew": "{\\mathrm{NO}{\\vphantom{A}}_{\\smash[t]{x }}}",
"type":"ce"
},
{
"tex": "Fe^n+",
"texNew": "{\\mathrm{Fe}{\\vphantom{A}}^{n +}}",
"type":"ce"
},
{
"tex": "x Na(NH4)HPO4 ->[Delta] (NaPO3)_x + x NH3 ^ + x H2O",
"texNew": "{x\\,\\mathrm{Na}(\\mathrm{NH}{\\vphantom{A}}_{\\smash[t]{4}})\\mathrm{HPO}{\\vphantom{A}}_{\\smash[t]{4}} {}\\mathrel{\\xrightarrow{\\mathrm{Delta}}}{} (\\mathrm{NaPO}{\\vphantom{A}}_{\\smash[t]{3}}){\\vphantom{A}}_{\\smash[t]{x }} {}+{} x\\,\\mathrm{NH}{\\vphantom{A}}_{\\smash[t]{3}} \\uparrow{} {}+{} x\\,\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}",
"type": "ce"
}
],
"Greek Characters": [
{
"tex": "mu-Cl",
"texNew": "{\\mathrm{mu}{-}\\mathrm{Cl}}",
"type":"ce"
},
{
"tex": "[Pt(\\eta^2-C2H4)Cl3]-",
"texNew": "{[\\mathrm{Pt}(\\mathrm{\\eta}{\\vphantom{A}}^{2}\\text{-}\\mathrm{C}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{4}})\\mathrm{Cl}{\\vphantom{A}}_{\\smash[t]{3}}]{\\vphantom{A}}^{-}}",
"type":"ce"
},
{
"tex": "\\beta +",
"texNew": "{\\mathrm{\\beta }{\\vphantom{A}}^{+}}",
"type":"ce"
},
{
"tex": "^40_18Ar + \\gamma{} + \\nu_e",
"texNew": "{{\\vphantom{A}}^{\\hphantom{40}}_{\\hphantom{18}}\\mkern-1.5mu{\\vphantom{A}}^{\\smash[t]{\\vphantom{2}}\\llap{40}}_{\\vphantom{2}\\llap{\\smash[t]{18}}}\\mathrm{Ar} {}+{} \\mathrm{\\gamma{}} {}+{} \\mathrm{\\nu}{\\vphantom{A}}_{\\smash[t]{e }}}",
"type": "ce"
}
],
"(Italic) Math": [
{
"tex": "NaOH(aq,$\\infty$)",
"texNew": "{\\mathrm{NaOH}\\mskip2mu (\\mathrm{aq},\\infty )}",
"type":"ce"
},
{
"tex": "Fe(CN)_{$\\frac{6}{2}$}",
"texNew": "{\\mathrm{Fe}(\\mathrm{CN}){\\vphantom{A}}_{\\smash[t]{\\frac{6}{2} }}}",
"type":"ce"
},
{
"tex": "X_{$i$}^{$x$}",
"texNew": "{\\mathrm{X}{\\vphantom{A}}_{\\smash[t]{i }}{\\vphantom{A}}^{x }}",
"type":"ce"
},
{
"tex": "X_$i$^$x$",
"texNew": "{\\mathrm{X}{\\vphantom{A}}_{\\smash[t]{i }}{\\vphantom{A}}^{x }}",
"type": "ce"
}
],
"Italic Text": [
{
"tex": "$cis${-}[PtCl2(NH3)2]",
"texNew": "{cis {\\text{-}}[\\mathrm{PtCl}{\\vphantom{A}}_{\\smash[t]{2}}(\\mathrm{NH}{\\vphantom{A}}_{\\smash[t]{3}}){\\vphantom{A}}_{\\smash[t]{2}}]}",
"type":"ce"
},
{
"tex": "CuS($hP12$)",
"texNew": "{\\mathrm{CuS}(hP12 )}",
"type": "ce"
}
],
"Upright Text, Escape Parsing": [
{
"tex": "{Gluconic Acid} + H2O2",
"texNew": "{{\\text{Gluconic Acid}} {}+{} \\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}{\\vphantom{A}}_{\\smash[t]{2}}}",
"type":"ce"
},
{
"tex": "X_{{red}}",
"texNew": "{\\mathrm{X}{\\vphantom{A}}_{\\smash[t]{\\text{red}}}}",
"type":"ce"
},
{
"tex": "{(+)}_589{-}[Co(en)3]Cl3",
"texNew": "{{\\text{(+)}}{\\vphantom{A}}_{\\smash[t]{589}}{\\text{-}}[\\mathrm{Co}(\\mathrm{en}){\\vphantom{A}}_{\\smash[t]{3}}]\\mathrm{Cl}{\\vphantom{A}}_{\\smash[t]{3}}}",
"type": "ce"
}
],
"Bonds": [
{
"tex": "C6H5-CHO",
"texNew": "{\\mathrm{C}{\\vphantom{A}}_{\\smash[t]{6}}\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{5}}{-}\\mathrm{CHO}}",
"type":"ce"
},
{
"tex": "A-B=C#D",
"texNew": "{\\mathrm{A}{-}\\mathrm{B}{=}\\mathrm{C}{\\equiv}\\mathrm{D}}",
"type":"ce"
},
{
"tex": "A\\bond{-}B\\bond{=}C\\bond{#}D",
"texNew": "{\\mathrm{A}{-}\\mathrm{B}{=}\\mathrm{C}{\\equiv}\\mathrm{D}}",
"type":"ce"
},
{
"tex": "A\\bond{1}B\\bond{2}C\\bond{3}D",
"texNew": "{\\mathrm{A}{-}\\mathrm{B}{=}\\mathrm{C}{\\equiv}\\mathrm{D}}",
"type":"ce"
},
{
"tex": "A\\bond{~}B\\bond{~-}C",
"texNew": "{\\mathrm{A}{\\tripledash}\\mathrm{B}{\\rlap{\\lower.1em{-}}\\raise.1em{\\tripledash}}\\mathrm{C}}",
"type":"ce"
},
{
"tex": "A\\bond{~--}B\\bond{~=}C\\bond{-~-}D",
"texNew": "{\\mathrm{A}{\\rlap{\\lower.2em{-}}\\rlap{\\raise.2em{\\tripledash}}-}\\mathrm{B}{\\rlap{\\lower.2em{-}}\\rlap{\\raise.2em{\\tripledash}}-}\\mathrm{C}{\\rlap{\\lower.2em{-}}\\rlap{\\raise.2em{-}}\\tripledash}\\mathrm{D}}",
"type":"ce"
},
{
"tex": "A\\bond{...}B\\bond{....}C",
"texNew": "{\\mathrm{A}{{\\cdot}{\\cdot}{\\cdot}}\\mathrm{B}{{\\cdot}{\\cdot}{\\cdot}{\\cdot}}\\mathrm{C}}",
"type":"ce"
},
{
"tex": "A\\bond{->}B\\bond{<-}C",
"texNew": "{\\mathrm{A}{\\rightarrow}\\mathrm{B}{\\leftarrow}\\mathrm{C}}",
"type": "ce"
}
],
"Addition Compounds": [
{
"tex": "KCr(SO4)2*12H2O",
"texNew": "{\\mathrm{KCr}(\\mathrm{SO}{\\vphantom{A}}_{\\smash[t]{4}}){\\vphantom{A}}_{\\smash[t]{2}}\\,{\\cdot}\\,12\\,\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}",
"type":"ce"
},
{
"tex": "KCr(SO4)2.12H2O",
"texNew": "{\\mathrm{KCr}(\\mathrm{SO}{\\vphantom{A}}_{\\smash[t]{4}}){\\vphantom{A}}_{\\smash[t]{2}}\\,{\\cdot}\\,12\\,\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}",
"type":"ce"
},
{
"tex": "KCr(SO4)2 * 12 H2O",
"texNew": "{\\mathrm{KCr}(\\mathrm{SO}{\\vphantom{A}}_{\\smash[t]{4}}){\\vphantom{A}}_{\\smash[t]{2}}\\,{\\cdot}\\,12\\,\\mathrm{H}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}}",
"type": "ce"
}
],
"Oxidation States": [
{
"tex": "Fe^{II}Fe^{III}2O4",
"texNew": "{\\mathrm{Fe}{\\vphantom{A}}^{\\mathrm{II}}\\mathrm{Fe}{\\vphantom{A}}^{\\mathrm{III}}{\\vphantom{A}}_{\\smash[t]{2}}\\mathrm{O}{\\vphantom{A}}_{\\smash[t]{4}}}",
"type": "ce"
}
],
"Unpaired Electrons, Radical Dots": [
{
"tex": "OCO^{.-}",
"texNew": "{\\mathrm{OCO}{\\vphantom{A}}^{\\mkern1mu \\bullet\\mkern1mu -}}",
"type":"ce"
},
{
"tex": "NO^{(2.)-}",
"texNew": "{\\mathrm{NO}{\\vphantom{A}}^{(2\\mkern1mu \\bullet\\mkern1mu )-}}",
"type": "ce"
}
],
"Kröger-Vink Notation": [
{
"tex": "Li^x_{Li,1-2x}Mg^._{Li,x}$V$'_{Li,x}Cl^x_{Cl}",
"texNew": "{\\mathrm{Li}{\\vphantom{A}}^{{\\times}}_{\\smash[t]{\\mathrm{Li}{,}\\mkern1mu 1-2x }}\\mathrm{Mg}{\\vphantom{A}}^{\\mkern1mu \\bullet\\mkern1mu }_{\\smash[t]{\\mathrm{Li}{,}\\mkern1mu x }}V {\\vphantom{A}}^{\\prime }_{\\smash[t]{\\mathrm{Li}{,}\\mkern1mu x }}\\mathrm{Cl}{\\vphantom{A}}^{{\\times}}_{\\smash[t]{\\mathrm{Cl}}}}",
"type":"ce"
},
{
"tex": "O''_{i,x}",
"texNew": "{\\mathrm{O}{\\vphantom{A}}^{\\prime \\prime }_{\\smash[t]{\\mathrm{i}{,}\\mkern1mu x }}}",
"type":"ce"
},
{
"tex": "M^{..}_i",
"texNew": "{\\mathrm{M}{\\vphantom{A}}^{\\mkern1mu \\bullet\\mkern1mu \\mkern1mu \\bullet\\mkern1mu }_{\\smash[t]{\\mathrm{i}}}}",
"type":"ce"
},
{
"tex": "$V$^{4'}_{Ti}",
"texNew": "{V {\\vphantom{A}}^{4\\prime }_{\\smash[t]{\\mathrm{Ti}}}}",
"type":"ce"
},
{
"tex": "V_{V,1}C_{C,0.8}$V$_{C,0.2}",
"texNew": "{\\mathrm{V}{\\vphantom{A}}_{\\smash[t]{\\mathrm{V}{,}\\mkern1mu 1}}\\mathrm{C}{\\vphantom{A}}_{\\smash[t]{\\mathrm{C}{,}\\mkern1mu 0.8}}V {\\vphantom{A}}_{\\smash[t]{\\mathrm{C}{,}\\mkern1mu 0.2}}}",
"type":"ce"
}
],
"Equation Operators": [
{
"tex": "A + B",
"texNew": "{\\mathrm{A} {}+{} \\mathrm{B}}",
"type":"ce"
},
{
"tex": "A - B",
"texNew": "{\\mathrm{A} {}-{} \\mathrm{B}}",
"type":"ce"
},
{
"tex": "A = B",
"texNew": "{\\mathrm{A} {}={} \\mathrm{B}}",
"type":"ce"
},
{
"tex": "A \\pm B",
"texNew": "{\\mathrm{A} {}\\pm{} \\mathrm{B}}",
"type": "ce"
}
],
"Precipitate and Gas": [
{
"tex": "SO4^2- + Ba^2+ -> BaSO4 v",
"texNew": "{\\mathrm{SO}{\\vphantom{A}}_{\\smash[t]{4}}{\\vphantom{A}}^{2-} {}+{} \\mathrm{Ba}{\\vphantom{A}}^{2+} {}\\mathrel{\\longrightarrow}{} \\mathrm{BaSO}{\\vphantom{A}}_{\\smash[t]{4}} \\downarrow{} }",
"type":"ce"
},
{
"tex": "A v B (v) -> B ^ B (^)",
"texNew": "{\\mathrm{A} \\downarrow{} ~\\mathrm{B} \\downarrow{} {}\\mathrel{\\longrightarrow}{} \\mathrm{B} \\uparrow{} ~\\mathrm{B} \\uparrow{} }",
"type": "ce"
}
],
"Other Symbols and Shortcuts": [
{
"tex": "NO^*",
"texNew": "{\\mathrm{NO}{\\vphantom{A}}^{*}}",
"type":"ce"
},
{
"tex": "1s^2-N",
"texNew": "{1\\mathrm{s}{\\vphantom{A}}^{2}\\text{-}\\mathrm{N}}",
"type":"ce"
},
{
"tex": "n-Pr",
"texNew": "{n \\text{-}\\mathrm{Pr}}",
"type":"ce"
},
{
"tex": "iPr",
"texNew": "{\\mathrm{iPr}}",
"type":"ce"
},
{
"tex": "\\ca Fe",
"texNew": "{{\\sim}\\mathrm{Fe}}",
"type":"ce"
},
{
"tex": "A, B, C; F",
"texNew": "{\\mathrm{A}{,}\\mkern6mu \\mathrm{B}{,}\\mkern6mu \\mathrm{C}{;}\\mkern6mu \\mathrm{F}}",
"type":"ce"
},
{
"tex": "{and others}",
"texNew": "{{\\text{and others}}}",
"type": "ce"
}
],
"Complex Examples": [
{
"tex": "Zn^2+ <=>[+ 2OH-][+ 2H+] $\\underset{\\text{amphoteres Hydroxid}}{\\ce{Zn(OH)2 v}}$ <=>[+ 2OH-][+ 2H+] $\\underset{\\text{Hydroxozikat}}{\\ce{[Zn(OH)4]^2-}}$",
"texNew": "{\\mathrm{Zn}{\\vphantom{A}}^{2+} {}\\mathrel{\\underset{\\lower6mu{ {}+{} 2\\,\\mathrm{H}{\\vphantom{A}}^{+}}}{\\overset{ {}+{} 2\\,\\mathrm{OH}{\\vphantom{A}}^{-}}{\\longrightleftharpoons}}}{} \\underset{\\text{amphoteres Hydroxid}}{\\ce{Zn(OH)2 v}} {}\\mathrel{\\underset{\\lower6mu{ {}+{} 2\\,\\mathrm{H}{\\vphantom{A}}^{+}}}{\\overset{ {}+{} 2\\,\\mathrm{OH}{\\vphantom{A}}^{-}}{\\longrightleftharpoons}}}{} \\underset{\\text{Hydroxozikat}}{\\ce{[Zn(OH)4]^2-}} }",
"type":"ce"
},
{
"tex": "$K = \\frac{[\\ce{Hg^2+}][\\ce{Hg}]}{[\\ce{Hg2^2+}]}$",
"texNew": "{K = \\frac{[\\ce{Hg^2+}][\\ce{Hg}]}{[\\ce{Hg2^2+}]} }",
"type":"ce"
},
{
"tex": "$K = \\ce{\\frac{[Hg^2+][Hg]}{[Hg2^2+]}}$",
"texNew": "{K = \\frac{[\\mathrm{Hg}{\\vphantom{A}}^{2+}][\\mathrm{Hg}]}{[\\mathrm{Hg}{\\vphantom{A}}_{\\smash[t]{2}}{\\vphantom{A}}^{2+}]}}",
"type":"ce"
},
{
"tex": "Hg^2+ ->[I-] $\\underset{\\mathrm{red}}{\\ce{HgI2}}$ ->[I-] $\\underset{\\mathrm{red}}{\\ce{[Hg^{II}I4]^2-}}$",
"texNew": "{\\mathrm{Hg}{\\vphantom{A}}^{2+} {}\\mathrel{\\xrightarrow{\\mathrm{I}{\\vphantom{A}}^{-}}}{} \\underset{\\mathrm{red}}{\\ce{HgI2}} {}\\mathrel{\\xrightarrow{\\mathrm{I}{\\vphantom{A}}^{-}}}{} \\underset{\\mathrm{red}}{\\ce{[Hg^{II}I4]^2-}} }",
"type":"ce"
}
],
"pu": [
{
"tex": "123 kJ",
"texNew": "{123~\\mathrm{kJ}}",
"type":"pu"
},
{
"tex": "123 mm2",
"texNew": "{123~\\mathrm{mm^{2}}}",
"type":"pu"
},
{
"tex": "123 J s",
"texNew": "{123~\\mathrm{J}\\mkern3mu \\mathrm{s}}",
"type":"pu"
},
{
"tex": "123 J*s",
"texNew": "{123~\\mathrm{J}\\mkern1mu{\\cdot}\\mkern1mu \\mathrm{s}}",
"type":"pu"
},
{
"tex": "123 kJ/mol",
"texNew": "{123~\\mathrm{kJ}/\\mathrm{mol}}",
"type":"pu"
},
{
"tex": "123 kJ//mol",
"texNew": "{123~\\mathchoice{\\textstyle\\frac{\\mathrm{kJ}}{\\mathrm{mol}}}{\\frac{\\mathrm{kJ}}{\\mathrm{mol}}}{\\frac{\\mathrm{kJ}}{\\mathrm{mol}}}{\\frac{\\mathrm{kJ}}{\\mathrm{mol}}}}",
"type":"pu"
},
{
"tex": "123 kJ mol^-1",
"texNew": "{123~\\mathrm{kJ}\\mkern3mu \\mathrm{mol^{-1}}}",
"type":"pu"
},
{
"tex": "123 kJ*mol-1",
"texNew": "{123~\\mathrm{kJ}\\mkern1mu{\\cdot}\\mkern1mu \\mathrm{mol^{-1}}}",
"type":"pu"
},
{
"tex": "123 kJ.mol-1",
"texNew": "{123~\\mathrm{kJ}\\mkern1mu{\\cdot}\\mkern1mu \\mathrm{mol^{-1}}}",
"type":"pu"
},
{
"tex": "1.2e3 kJ",
"texNew": "{1.2\\cdot 10^{3}~\\mathrm{kJ}}",
"type":"pu"
},
{
"tex": "1,2e3 kJ",
"texNew": "{1{,}2\\cdot 10^{3}~\\mathrm{kJ}}",
"type":"pu"
},
{
"tex": "1.2E3 kJ",
"texNew": "{1.2\\times 10^{3}~\\mathrm{kJ}}",
"type":"pu"
},
{
"tex": "1,2E3 kJ",
"texNew": "{1{,}2\\times 10^{3}~\\mathrm{kJ}}",
"type":"pu"
},
{
"tex": "1234",
"texNew": "{1234}",
"type":"pu"
},
{
"tex": "12345",
"texNew": "{12\\mkern2mu 345}",
"type":"pu"
},
{
"tex": "1\u00B0C",
"texNew": "{1~\\mathrm{{}^{\\circ}C}}",
"type":"pu"
},
{
"tex": "23.4782(32) m",
"texNew": "{23.4782(32)~\\mathrm{m}}",
"type":"pu"
},
{
"tex": "8.00001 \\pm 0.00005 nm",
"texNew": "{8.000\\mkern2mu 01 {}\\pm{} 0.000\\mkern2mu 05~\\mathrm{nm}}",
"type":"pu"
},
{
"tex": ".25",
"texNew": "{.25}",
"type":"pu"
},
{
"tex": "1 mol ",
"texNew": "{1~\\mathrm{mol}~}",
"type":"pu"
},
{
"tex": "123 l//100km",
"texNew": "{123~\\mathchoice{\\textstyle\\frac{\\mathrm{l}}{100~\\mathrm{km}}}{\\frac{\\mathrm{l}}{100~\\mathrm{km}}}{\\frac{\\mathrm{l}}{100~\\mathrm{km}}}{\\frac{\\mathrm{l}}{100~\\mathrm{km}}}}",
"type":"pu"
}
]
}