服务:Spring Boot 3.2 + docx4j 8.3.9,POST /api/v3/server_office_file/file/parse
接受 .docx,返回 HTML(公式 → LaTeX,图片 → RustFS 外链)
公式支持三类来源:
- OMML(Word 2007+ 公式) → Python omml_mathml2latex
- MathType OLE 对象 → Ruby mathtype2mathml → MathML → LaTeX
- Word EQ 域(老教辅主力) → EqToLatexUtil(本次新增)
本次改动:
1. 新增 java-svc/.../core/EqToLatexUtil.java:
移植自 jby backend-services office-core,EQ 语法 → LaTeX
(\f→\dfrac、\r→\sqrt、\i→\int/\sum/\prod、\s→^{}/_{}、\b→\left\right、
\a→array、\x→\boxed/\overline、\o\al 堆叠、圈码、化学符号、极限、连等式)
品牌标记 JBYU* → WPU*,与采集端一致
2. CtoMathFinder:接上 EQ 分支(原为 not-ported 桩),域代码经
EqToLatexUtil.convert 转 LaTeX 写入 eqLatexMap(WPEQNUM 占位符)
3. WordParseUtil.dealPlaceholder:EQ 的 LaTeX 同样加 \( \) 包裹
(原先因 WPEQNUM 例外不加包裹,前端/MathJax 不渲染)
修前症状:教辅 Word 里分式/方程组/上下标整段丢失(如 {−1/3, 1} 变成 {-,1})
修后实测:46 份教辅 2908 处 EQ 域全部还原,公式总数 2954 处、零未包裹残留
安全:application.yml(含 RustFS 密钥)不入库,提供 application.yml.example;
OssService 的 @Value 默认密钥已清空
851 lines
40 KiB
XML
851 lines
40 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
||
<xsl:stylesheet version="1.0"
|
||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
|
||
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||
xmlns:mml="http://www.w3.org/1998/Math/MathML"
|
||
xmlns="http://www.w3.org/1998/Math/MathML"
|
||
exclude-result-prefixes="m w">
|
||
|
||
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
|
||
|
||
<!-- Root template -->
|
||
<xsl:template match="/">
|
||
<xsl:apply-templates select="//m:oMath"/>
|
||
</xsl:template>
|
||
|
||
<!-- Main math element -->
|
||
<xsl:template match="m:oMath">
|
||
<math>
|
||
<xsl:apply-templates select="*"/>
|
||
</math>
|
||
</xsl:template>
|
||
|
||
<!-- Text run -->
|
||
<xsl:template match="m:r">
|
||
<xsl:apply-templates select="m:t"/>
|
||
</xsl:template>
|
||
|
||
<!-- Text content -->
|
||
<xsl:template match="m:t">
|
||
<!-- Get style from parent <m:r><m:rPr><m:sty> -->
|
||
<xsl:variable name="sty" select="../m:rPr/m:sty/@m:val"/>
|
||
|
||
<!-- Get Word run properties (w:rPr) - check for italic (w:i) -->
|
||
<!-- w:rPr is a sibling of m:t (both are children of m:r) -->
|
||
<!-- CRITICAL: w:i with @w:val="0" or "false" means NOT italic! -->
|
||
<!-- Only treat as italic if w:i exists AND val is not "0" or "false" -->
|
||
<xsl:variable name="has_word_italic" select="boolean(../w:rPr/w:i[not(@w:val = '0') and not(@w:val = 'false')])"/>
|
||
|
||
<!-- Get math "normal" style (m:nor) - text mode button -->
|
||
<xsl:variable name="has_math_normal" select="boolean(../m:rPr/m:nor)"/>
|
||
|
||
<xsl:choose>
|
||
<xsl:when test="normalize-space(.) = ''">
|
||
<!-- Skip empty text -->
|
||
</xsl:when>
|
||
<xsl:when test="number(.) = number(.)">
|
||
<mn><xsl:value-of select="normalize-space(.)"/></mn>
|
||
</xsl:when>
|
||
<!-- Fence delimiters: parentheses, brackets, pipes (with fence="true") -->
|
||
<xsl:when test=". = '(' or . = ')' or . = '[' or . = ']' or . = '|'">
|
||
<mo fence="true"><xsl:value-of select="normalize-space(.)"/></mo>
|
||
</xsl:when>
|
||
<!-- Plain operators (no fence attribute) -->
|
||
<!-- NOTE: { and } are converted to markers to distinguish source braces from generated LaTeX braces -->
|
||
<xsl:when test=". = '{' or . = '}' or . = '+' or . = '-' or . = '=' or . = '<' or . = '>' or . = '±' or . = '≠' or . = '≤' or . = '≥' or . = '×' or . = '÷' or . = '∞'">
|
||
<mo>
|
||
<xsl:choose>
|
||
<xsl:when test=". = '{'">__LBRACE__</xsl:when>
|
||
<xsl:when test=". = '}'">__RBRACE__</xsl:when>
|
||
<xsl:otherwise><xsl:value-of select="normalize-space(.)"/></xsl:otherwise>
|
||
</xsl:choose>
|
||
</mo>
|
||
</xsl:when>
|
||
<!-- Mixed operators/delimiters (like ")+" or ")=" or "(A" or "={" or ",}") -->
|
||
<!-- Check if string contains multiple operator/delimiter characters -->
|
||
<xsl:when test="string-length(normalize-space(.)) > 1 and (contains(., '(') or contains(., ')') or contains(., '[') or contains(., ']') or contains(., '{') or contains(., '}') or contains(., '+') or contains(., '-') or contains(., '=') or contains(., '<') or contains(., '>'))">
|
||
<!-- Split into individual characters and classify each one -->
|
||
<!-- Pass style info so letters get proper mathvariant (e.g., mathvariant="normal" for m:nor text mode) -->
|
||
<xsl:call-template name="split-operators">
|
||
<xsl:with-param name="text" select="normalize-space(.)"/>
|
||
<xsl:with-param name="math_variant">
|
||
<xsl:choose>
|
||
<xsl:when test="$has_word_italic">italic</xsl:when>
|
||
<xsl:when test="$sty = 'b' or $sty = 'bi'">bold</xsl:when>
|
||
<xsl:when test="$sty = 'p'">normal</xsl:when>
|
||
<xsl:when test="$has_math_normal">normal</xsl:when>
|
||
<xsl:otherwise></xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:with-param>
|
||
</xsl:call-template>
|
||
</xsl:when>
|
||
<xsl:when test="contains(., '|')">
|
||
<!-- Special handling for text containing | (like "x|x", "A|B", or "||") -->
|
||
<!-- Split by | and create separate elements with fence="true" for absolute values -->
|
||
<xsl:variable name="before" select="substring-before(., '|')"/>
|
||
<xsl:variable name="after" select="substring-after(., '|')"/>
|
||
<!-- Process before part (plain text) -->
|
||
<xsl:if test="$before != ''">
|
||
<xsl:choose>
|
||
<!-- If before part contains operators or is multi-char, use split-operators -->
|
||
<xsl:when test="string-length($before) > 1">
|
||
<xsl:call-template name="split-operators">
|
||
<xsl:with-param name="text" select="$before"/>
|
||
</xsl:call-template>
|
||
</xsl:when>
|
||
<!-- Single character - output as mi -->
|
||
<xsl:otherwise>
|
||
<mi><xsl:value-of select="$before"/></mi>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:if>
|
||
<!-- Output the | as fence for potential absolute value -->
|
||
<mo fence="true">|</mo>
|
||
<!-- Process after part recursively if it contains more | or operators -->
|
||
<xsl:if test="$after != ''">
|
||
<xsl:choose>
|
||
<!-- If after part is just |, output as fence -->
|
||
<xsl:when test="$after = '|'">
|
||
<mo fence="true">|</mo>
|
||
</xsl:when>
|
||
<!-- If after part contains more |, recursively process this m:t content -->
|
||
<xsl:when test="contains($after, '|')">
|
||
<xsl:call-template name="process-text-with-pipe">
|
||
<xsl:with-param name="text" select="$after"/>
|
||
</xsl:call-template>
|
||
</xsl:when>
|
||
<!-- If after part contains operators, use split-operators -->
|
||
<xsl:when test="string-length($after) > 1 and (contains($after, '+') or contains($after, '-') or contains($after, '='))">
|
||
<xsl:call-template name="split-operators">
|
||
<xsl:with-param name="text" select="$after"/>
|
||
</xsl:call-template>
|
||
</xsl:when>
|
||
<!-- Single character or simple text - output as mi -->
|
||
<xsl:otherwise>
|
||
<mi><xsl:value-of select="$after"/></mi>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:if>
|
||
</xsl:when>
|
||
<xsl:when test="string-length(normalize-space(.)) = 1 and not(contains('0123456789', .))">
|
||
<mi>
|
||
<!-- Apply mathvariant based on style -->
|
||
<!-- Note: m:sty='i' (italic) is NOT handled here as italic is the default for variables -->
|
||
<!-- Priority: italic (w:i explicit) > bold > plain (m:sty='p') > normal (m:nor) -->
|
||
<xsl:choose>
|
||
<xsl:when test="$has_word_italic">
|
||
<xsl:attribute name="mathvariant">italic</xsl:attribute>
|
||
</xsl:when>
|
||
<xsl:when test="$sty = 'b' or $sty = 'bi'">
|
||
<xsl:attribute name="mathvariant">bold</xsl:attribute>
|
||
</xsl:when>
|
||
<xsl:when test="$sty = 'p'">
|
||
<xsl:attribute name="mathvariant">normal</xsl:attribute>
|
||
</xsl:when>
|
||
<xsl:when test="$has_math_normal">
|
||
<xsl:attribute name="mathvariant">normal</xsl:attribute>
|
||
</xsl:when>
|
||
</xsl:choose>
|
||
<xsl:value-of select="normalize-space(.)"/>
|
||
</mi>
|
||
</xsl:when>
|
||
<xsl:otherwise>
|
||
<mi>
|
||
<!-- Apply mathvariant for multi-character identifiers too -->
|
||
<!-- Note: m:sty='i' (italic) is NOT handled here as italic is the default for variables -->
|
||
<!-- Priority: italic (w:i explicit) > bold > plain (m:sty='p') > normal (m:nor) -->
|
||
<xsl:choose>
|
||
<xsl:when test="$has_word_italic">
|
||
<xsl:attribute name="mathvariant">italic</xsl:attribute>
|
||
</xsl:when>
|
||
<xsl:when test="$sty = 'b' or $sty = 'bi'">
|
||
<xsl:attribute name="mathvariant">bold</xsl:attribute>
|
||
</xsl:when>
|
||
<xsl:when test="$sty = 'p'">
|
||
<xsl:attribute name="mathvariant">normal</xsl:attribute>
|
||
</xsl:when>
|
||
<xsl:when test="$has_math_normal">
|
||
<xsl:attribute name="mathvariant">normal</xsl:attribute>
|
||
</xsl:when>
|
||
</xsl:choose>
|
||
<xsl:value-of select="normalize-space(.)"/>
|
||
</mi>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:template>
|
||
|
||
<!-- Superscript -->
|
||
<xsl:template match="m:sSup">
|
||
<msup>
|
||
<xsl:apply-templates select="m:e"/>
|
||
<xsl:apply-templates select="m:sup"/>
|
||
</msup>
|
||
</xsl:template>
|
||
|
||
<!-- Subscript -->
|
||
<xsl:template match="m:sSub">
|
||
<msub>
|
||
<xsl:apply-templates select="m:e"/>
|
||
<xsl:apply-templates select="m:sub"/>
|
||
</msub>
|
||
</xsl:template>
|
||
|
||
<!-- Sub-superscript -->
|
||
<xsl:template match="m:sSubSup">
|
||
<msubsup>
|
||
<xsl:apply-templates select="m:e"/>
|
||
<xsl:apply-templates select="m:sub"/>
|
||
<xsl:apply-templates select="m:sup"/>
|
||
</msubsup>
|
||
</xsl:template>
|
||
|
||
<!-- Pre-script (left-side subscript/superscript) -->
|
||
<!-- Used for nuclear notation like _{2}^{3}He -->
|
||
<xsl:template match="m:sPre">
|
||
<mmultiscripts>
|
||
<xsl:apply-templates select="m:e"/>
|
||
<mprescripts/>
|
||
<xsl:apply-templates select="m:sub"/>
|
||
<xsl:apply-templates select="m:sup"/>
|
||
</mmultiscripts>
|
||
</xsl:template>
|
||
|
||
<!-- Fraction -->
|
||
<!-- m:type values: bar (default), noBar, lin (linear), skw (skewed) -->
|
||
<xsl:template match="m:f">
|
||
<xsl:variable name="type" select="m:fPr/m:type/@m:val"/>
|
||
<mfrac>
|
||
<!-- Add linethickness="0" for noBar fractions (stacked without bar) -->
|
||
<xsl:if test="$type = 'noBar'">
|
||
<xsl:attribute name="linethickness">0</xsl:attribute>
|
||
</xsl:if>
|
||
<!-- Add bevelled="true" for linear fractions -->
|
||
<xsl:if test="$type = 'lin'">
|
||
<xsl:attribute name="bevelled">true</xsl:attribute>
|
||
</xsl:if>
|
||
<xsl:apply-templates select="m:num"/>
|
||
<xsl:apply-templates select="m:den"/>
|
||
</mfrac>
|
||
</xsl:template>
|
||
|
||
<!-- Radical -->
|
||
<xsl:template match="m:rad">
|
||
<xsl:choose>
|
||
<xsl:when test="m:deg">
|
||
<mroot>
|
||
<xsl:apply-templates select="m:e"/>
|
||
<xsl:apply-templates select="m:deg"/>
|
||
</mroot>
|
||
</xsl:when>
|
||
<xsl:otherwise>
|
||
<msqrt>
|
||
<xsl:apply-templates select="m:e"/>
|
||
</msqrt>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:template>
|
||
|
||
<!-- Parentheses/delimiters -->
|
||
<xsl:template match="m:d">
|
||
<mrow>
|
||
<!-- Opening delimiter: skip if begChr has empty or whitespace-only val -->
|
||
<xsl:choose>
|
||
<xsl:when test="not(m:dPr/m:begChr)">
|
||
<mo fence="true">(</mo>
|
||
</xsl:when>
|
||
<xsl:when test="normalize-space(m:dPr/m:begChr/@m:val) != ''">
|
||
<mo fence="true">
|
||
<xsl:value-of select="m:dPr/m:begChr/@m:val"/>
|
||
</mo>
|
||
</xsl:when>
|
||
<!-- begChr with empty/whitespace val → no opening delimiter -->
|
||
</xsl:choose>
|
||
<!-- Handle multiple m:e elements with separators -->
|
||
<xsl:for-each select="m:e">
|
||
<xsl:apply-templates select="."/>
|
||
<!-- Insert separator | between elements (except after the last one) -->
|
||
<xsl:if test="position() != last()">
|
||
<mo>|</mo>
|
||
</xsl:if>
|
||
</xsl:for-each>
|
||
<!-- Closing delimiter: skip if endChr has empty or whitespace-only val -->
|
||
<xsl:choose>
|
||
<xsl:when test="not(m:dPr/m:endChr)">
|
||
<mo fence="true">)</mo>
|
||
</xsl:when>
|
||
<xsl:when test="normalize-space(m:dPr/m:endChr/@m:val) != ''">
|
||
<mo fence="true">
|
||
<xsl:value-of select="m:dPr/m:endChr/@m:val"/>
|
||
</mo>
|
||
</xsl:when>
|
||
<!-- endChr with empty/whitespace val → no closing delimiter -->
|
||
</xsl:choose>
|
||
</mrow>
|
||
</xsl:template>
|
||
|
||
<!-- Function -->
|
||
<xsl:template match="m:func">
|
||
<mrow>
|
||
<xsl:apply-templates select="m:fName"/>
|
||
<xsl:apply-templates select="m:e"/>
|
||
</mrow>
|
||
</xsl:template>
|
||
|
||
<!-- Matrix -->
|
||
<xsl:template match="m:m">
|
||
<mtable>
|
||
<xsl:apply-templates select="m:mr"/>
|
||
</mtable>
|
||
</xsl:template>
|
||
|
||
<!-- Matrix row -->
|
||
<xsl:template match="m:mr">
|
||
<mtr>
|
||
<xsl:apply-templates select="m:e"/>
|
||
</mtr>
|
||
</xsl:template>
|
||
|
||
<!-- Equation array (for piecewise functions) -->
|
||
<xsl:template match="m:eqArr">
|
||
<mtable>
|
||
<!-- Each <m:e> child represents one row in the piecewise function -->
|
||
<xsl:for-each select="m:e">
|
||
<mtr>
|
||
<mtd>
|
||
<xsl:apply-templates select="node()"/>
|
||
</mtd>
|
||
</mtr>
|
||
</xsl:for-each>
|
||
</mtable>
|
||
</xsl:template>
|
||
|
||
<!-- Bar (overbar/underbar) -->
|
||
<!-- OMML spec: no pos attribute = underline (default), pos="top" = overline, pos="bot" = underline -->
|
||
<xsl:template match="m:bar">
|
||
<xsl:choose>
|
||
<xsl:when test="m:barPr/m:pos/@m:val = 'top'">
|
||
<mover>
|
||
<xsl:apply-templates select="m:e"/>
|
||
<mo>‾</mo>
|
||
</mover>
|
||
</xsl:when>
|
||
<xsl:otherwise>
|
||
<!-- Default: no pos attribute or pos="bot" both mean underline -->
|
||
<munder>
|
||
<xsl:apply-templates select="m:e"/>
|
||
<mo>‾</mo>
|
||
</munder>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:template>
|
||
|
||
<!-- Limits -->
|
||
<!-- limLow: Lower limit position (content BELOW the base, like underscript) -->
|
||
<xsl:template match="m:limLow">
|
||
<munder>
|
||
<xsl:apply-templates select="m:e"/>
|
||
<xsl:apply-templates select="m:lim"/>
|
||
</munder>
|
||
</xsl:template>
|
||
|
||
<!-- limUpp: Upper limit position (content ABOVE the base, like overscript) -->
|
||
<xsl:template match="m:limUpp">
|
||
<mover>
|
||
<xsl:apply-templates select="m:e"/>
|
||
<xsl:apply-templates select="m:lim"/>
|
||
</mover>
|
||
</xsl:template>
|
||
|
||
<xsl:template match="m:limUppLow">
|
||
<munderover>
|
||
<xsl:apply-templates select="m:e"/>
|
||
<xsl:apply-templates select="m:lim[1]"/>
|
||
<xsl:apply-templates select="m:lim[2]"/>
|
||
</munderover>
|
||
</xsl:template>
|
||
|
||
<!-- N-ary operators (integrals, sums, products) -->
|
||
<xsl:template match="m:nary">
|
||
<xsl:variable name="chr" select="m:naryPr/m:chr/@m:val"/>
|
||
<xsl:variable name="limLoc" select="m:naryPr/m:limLoc/@m:val"/>
|
||
<xsl:variable name="subHide" select="m:naryPr/m:subHide/@m:val"/>
|
||
<xsl:variable name="supHide" select="m:naryPr/m:supHide/@m:val"/>
|
||
|
||
<xsl:choose>
|
||
<!-- Both limits hidden: just operator and integrand -->
|
||
<xsl:when test="$subHide = '1' and $supHide = '1'">
|
||
<mrow>
|
||
<mo>
|
||
<xsl:choose>
|
||
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||
<xsl:otherwise>∫</xsl:otherwise>
|
||
</xsl:choose>
|
||
</mo>
|
||
<xsl:apply-templates select="m:e"/>
|
||
</mrow>
|
||
</xsl:when>
|
||
|
||
<!-- Limits as subscript/superscript (default for integrals) -->
|
||
<xsl:when test="not($limLoc) or $limLoc = 'subSup'">
|
||
<xsl:choose>
|
||
<!-- Both sub and sup present -->
|
||
<xsl:when test="m:sub and m:sup">
|
||
<msubsup>
|
||
<mo>
|
||
<xsl:choose>
|
||
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||
<xsl:otherwise>∫</xsl:otherwise>
|
||
</xsl:choose>
|
||
</mo>
|
||
<xsl:apply-templates select="m:sub"/>
|
||
<xsl:apply-templates select="m:sup"/>
|
||
</msubsup>
|
||
<xsl:apply-templates select="m:e"/>
|
||
</xsl:when>
|
||
<!-- Only subscript -->
|
||
<xsl:when test="m:sub">
|
||
<msub>
|
||
<mo>
|
||
<xsl:choose>
|
||
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||
<xsl:otherwise>∫</xsl:otherwise>
|
||
</xsl:choose>
|
||
</mo>
|
||
<xsl:apply-templates select="m:sub"/>
|
||
</msub>
|
||
<xsl:apply-templates select="m:e"/>
|
||
</xsl:when>
|
||
<!-- Only superscript -->
|
||
<xsl:when test="m:sup">
|
||
<msup>
|
||
<mo>
|
||
<xsl:choose>
|
||
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||
<xsl:otherwise>∫</xsl:otherwise>
|
||
</xsl:choose>
|
||
</mo>
|
||
<xsl:apply-templates select="m:sup"/>
|
||
</msup>
|
||
<xsl:apply-templates select="m:e"/>
|
||
</xsl:when>
|
||
<!-- No limits -->
|
||
<xsl:otherwise>
|
||
<mrow>
|
||
<mo>
|
||
<xsl:choose>
|
||
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||
<xsl:otherwise>∫</xsl:otherwise>
|
||
</xsl:choose>
|
||
</mo>
|
||
<xsl:apply-templates select="m:e"/>
|
||
</mrow>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:when>
|
||
|
||
<!-- Limits as under/over (for sums, products) -->
|
||
<xsl:when test="$limLoc = 'undOvr'">
|
||
<mrow>
|
||
<xsl:choose>
|
||
<!-- Both under and over -->
|
||
<xsl:when test="m:sub and m:sup and not($subHide = '1') and not($supHide = '1')">
|
||
<munderover>
|
||
<mo>
|
||
<xsl:choose>
|
||
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||
<xsl:otherwise>∫</xsl:otherwise>
|
||
</xsl:choose>
|
||
</mo>
|
||
<xsl:apply-templates select="m:sub"/>
|
||
<xsl:apply-templates select="m:sup"/>
|
||
</munderover>
|
||
</xsl:when>
|
||
<!-- Only under -->
|
||
<xsl:when test="m:sub and not($subHide = '1')">
|
||
<munder>
|
||
<mo>
|
||
<xsl:choose>
|
||
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||
<xsl:otherwise>∫</xsl:otherwise>
|
||
</xsl:choose>
|
||
</mo>
|
||
<xsl:apply-templates select="m:sub"/>
|
||
</munder>
|
||
</xsl:when>
|
||
<!-- Only over -->
|
||
<xsl:when test="m:sup and not($supHide = '1')">
|
||
<mover>
|
||
<mo>
|
||
<xsl:choose>
|
||
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||
<xsl:otherwise>∫</xsl:otherwise>
|
||
</xsl:choose>
|
||
</mo>
|
||
<xsl:apply-templates select="m:sup"/>
|
||
</mover>
|
||
</xsl:when>
|
||
<!-- No visible limits -->
|
||
<xsl:otherwise>
|
||
<mo>
|
||
<xsl:choose>
|
||
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||
<xsl:otherwise>∫</xsl:otherwise>
|
||
</xsl:choose>
|
||
</mo>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
<xsl:apply-templates select="m:e"/>
|
||
</mrow>
|
||
</xsl:when>
|
||
</xsl:choose>
|
||
</xsl:template>
|
||
|
||
<!-- Accent (vector arrows, dots, etc.) -->
|
||
<xsl:template match="m:acc">
|
||
<xsl:variable name="chr" select="m:accPr/m:chr/@m:val"/>
|
||
<mover>
|
||
<!-- Add data attribute to distinguish default accents from explicit ones -->
|
||
<xsl:if test="not($chr)">
|
||
<xsl:attribute name="data-default-accent">true</xsl:attribute>
|
||
</xsl:if>
|
||
<xsl:apply-templates select="m:e"/>
|
||
<mo>
|
||
<xsl:choose>
|
||
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||
<xsl:otherwise>⃗</xsl:otherwise><!-- Default accent (should be hat in most cases) -->
|
||
</xsl:choose>
|
||
</mo>
|
||
</mover>
|
||
</xsl:template>
|
||
|
||
<!-- Group character (for over/under arrows with text) -->
|
||
<xsl:template match="m:groupChr">
|
||
<xsl:variable name="chr" select="m:groupChrPr/m:chr/@m:val"/>
|
||
<xsl:variable name="pos" select="m:groupChrPr/m:pos/@m:val"/>
|
||
<xsl:variable name="vertJc" select="m:groupChrPr/m:vertJc/@m:val"/>
|
||
|
||
<xsl:choose>
|
||
<!-- vertJc='bot': Character at bottom (base), content above → mover(char, content) -->
|
||
<!-- This is used for extensible arrows like \xrightarrow[]{text} or \overset{text}{arrow} -->
|
||
<xsl:when test="$vertJc = 'bot'">
|
||
<mover accent="false">
|
||
<mo stretchy="true">
|
||
<xsl:choose>
|
||
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||
<xsl:otherwise>→</xsl:otherwise>
|
||
</xsl:choose>
|
||
</mo>
|
||
<xsl:apply-templates select="m:e"/>
|
||
</mover>
|
||
</xsl:when>
|
||
<!-- pos='bot': Character position at bottom, content above → munder -->
|
||
<xsl:when test="$pos = 'bot'">
|
||
<munder>
|
||
<xsl:apply-templates select="m:e"/>
|
||
<mo>
|
||
<xsl:choose>
|
||
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||
<xsl:otherwise>⏟</xsl:otherwise><!-- Default underbrace -->
|
||
</xsl:choose>
|
||
</mo>
|
||
</munder>
|
||
</xsl:when>
|
||
<!-- pos='top' (or default): Character above content → mover(content, char) -->
|
||
<xsl:otherwise>
|
||
<mover>
|
||
<xsl:apply-templates select="m:e"/>
|
||
<mo>
|
||
<xsl:choose>
|
||
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||
<xsl:otherwise>⏞</xsl:otherwise><!-- Default overbrace -->
|
||
</xsl:choose>
|
||
</mo>
|
||
</mover>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:template>
|
||
|
||
<!-- Box (but detect if it contains groupChr for reaction arrows) -->
|
||
<xsl:template match="m:box">
|
||
<xsl:choose>
|
||
<!-- Skip boxes with opEmu="1" (operator emulation - should be transparent) -->
|
||
<!-- This means process all children in m:e transparently -->
|
||
<xsl:when test="m:boxPr/m:opEmu/@m:val='1'">
|
||
<xsl:apply-templates select="m:e/*"/>
|
||
</xsl:when>
|
||
|
||
<!-- If box contains ONLY a single direct groupChr, handle specially -->
|
||
<!-- This is for cases like reaction arrows: text above/below extensible arrow -->
|
||
<xsl:when test="count(m:e/*) = 1 and m:e/m:groupChr">
|
||
<xsl:variable name="groupChr" select="m:e/m:groupChr"/>
|
||
<xsl:variable name="arrow" select="$groupChr/m:groupChrPr/m:chr/@m:val"/>
|
||
<xsl:variable name="vertJc" select="$groupChr/m:groupChrPr/m:vertJc/@m:val"/>
|
||
<xsl:variable name="pos" select="$groupChr/m:groupChrPr/m:pos/@m:val"/>
|
||
|
||
<xsl:choose>
|
||
<xsl:when test="$vertJc = 'bot'">
|
||
<!-- Arrow at bottom with text above (like \xrightarrow[]{text}) -->
|
||
<!-- For extensible arrows: mover with arrow base, text above -->
|
||
<mover accent="false">
|
||
<mo stretchy="true">
|
||
<xsl:choose>
|
||
<xsl:when test="$arrow"><xsl:value-of select="$arrow"/></xsl:when>
|
||
<xsl:otherwise>→</xsl:otherwise>
|
||
</xsl:choose>
|
||
</mo>
|
||
<xsl:apply-templates select="$groupChr/m:e"/>
|
||
</mover>
|
||
</xsl:when>
|
||
<xsl:when test="$pos = 'top'">
|
||
<!-- Arrow at top: \overrightarrow pattern -->
|
||
<!-- mover with content base, arrow above -->
|
||
<mover>
|
||
<xsl:apply-templates select="$groupChr/m:e"/>
|
||
<mo>
|
||
<xsl:choose>
|
||
<xsl:when test="$arrow"><xsl:value-of select="$arrow"/></xsl:when>
|
||
<xsl:otherwise>→</xsl:otherwise>
|
||
</xsl:choose>
|
||
</mo>
|
||
</mover>
|
||
</xsl:when>
|
||
<xsl:otherwise>
|
||
<!-- Default: Vector arrow over content -->
|
||
<mover>
|
||
<xsl:apply-templates select="$groupChr/m:e"/>
|
||
<mo>
|
||
<xsl:choose>
|
||
<xsl:when test="$arrow"><xsl:value-of select="$arrow"/></xsl:when>
|
||
<xsl:otherwise>→</xsl:otherwise>
|
||
</xsl:choose>
|
||
</mo>
|
||
</mover>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:when>
|
||
|
||
<!-- Skip phantom/layout boxes (argSz with negative value) -->
|
||
<!-- These are used for sizing/spacing control, not actual visible boxes -->
|
||
<xsl:when test="m:e/m:argPr/m:argSz/@m:val < 0">
|
||
<xsl:apply-templates select="m:e"/>
|
||
</xsl:when>
|
||
|
||
<!-- Regular box -->
|
||
<xsl:otherwise>
|
||
<menclose notation="box">
|
||
<xsl:apply-templates select="m:e"/>
|
||
</menclose>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:template>
|
||
|
||
<!-- Border box with strikethrough and border notation support -->
|
||
<xsl:template match="m:borderBox">
|
||
<xsl:variable name="props" select="m:borderBoxPr"/>
|
||
|
||
<!-- Check for strikethrough properties -->
|
||
<xsl:variable name="strikeBLTR" select="boolean($props/m:strikeBLTR)"/>
|
||
<xsl:variable name="strikeTLBR" select="boolean($props/m:strikeTLBR)"/>
|
||
<xsl:variable name="strikeH" select="boolean($props/m:strikeH)"/>
|
||
<xsl:variable name="strikeV" select="boolean($props/m:strikeV)"/>
|
||
|
||
<!-- Check for border hiding properties -->
|
||
<xsl:variable name="hideTop" select="boolean($props/m:hideTop)"/>
|
||
<xsl:variable name="hideBot" select="boolean($props/m:hideBot)"/>
|
||
<xsl:variable name="hideLeft" select="boolean($props/m:hideLeft)"/>
|
||
<xsl:variable name="hideRight" select="boolean($props/m:hideRight)"/>
|
||
|
||
<!-- Determine notation attribute -->
|
||
<xsl:variable name="notation">
|
||
<xsl:choose>
|
||
<!-- Combined diagonal strikes → xcancel -->
|
||
<xsl:when test="$strikeBLTR and $strikeTLBR">updiagonalstrike downdiagonalstrike</xsl:when>
|
||
|
||
<!-- Single diagonal strike BLTR → cancel (updiagonal) -->
|
||
<xsl:when test="$strikeBLTR">updiagonalstrike</xsl:when>
|
||
|
||
<!-- Single diagonal strike TLBR → bcancel (downdiagonal) -->
|
||
<xsl:when test="$strikeTLBR">downdiagonalstrike</xsl:when>
|
||
|
||
<!-- Combined horizontal and vertical → cancel -->
|
||
<xsl:when test="$strikeH and $strikeV">horizontalstrike verticalstrike</xsl:when>
|
||
|
||
<!-- Single horizontal strike → cancel -->
|
||
<xsl:when test="$strikeH">horizontalstrike</xsl:when>
|
||
|
||
<!-- Single vertical strike → bcancel -->
|
||
<xsl:when test="$strikeV">verticalstrike</xsl:when>
|
||
|
||
<!-- Single side visible -->
|
||
<xsl:when test="not($hideTop) and $hideBot and $hideLeft and $hideRight">top</xsl:when>
|
||
<xsl:when test="$hideTop and not($hideBot) and $hideLeft and $hideRight">bottom</xsl:when>
|
||
<xsl:when test="$hideTop and $hideBot and not($hideLeft) and $hideRight">left</xsl:when>
|
||
<xsl:when test="$hideTop and $hideBot and $hideLeft and not($hideRight)">right</xsl:when>
|
||
|
||
<!-- Two sides visible -->
|
||
<xsl:when test="not($hideTop) and not($hideBot) and $hideLeft and $hideRight">top bottom</xsl:when>
|
||
<xsl:when test="not($hideTop) and $hideBot and not($hideLeft) and $hideRight">top left</xsl:when>
|
||
<xsl:when test="not($hideTop) and $hideBot and $hideLeft and not($hideRight)">top right</xsl:when>
|
||
<xsl:when test="$hideTop and not($hideBot) and not($hideLeft) and $hideRight">bottom left</xsl:when>
|
||
<xsl:when test="$hideTop and not($hideBot) and $hideLeft and not($hideRight)">bottom right</xsl:when>
|
||
<xsl:when test="$hideTop and $hideBot and not($hideLeft) and not($hideRight)">left right</xsl:when>
|
||
|
||
<!-- Three sides visible -->
|
||
<xsl:when test="not($hideTop) and not($hideBot) and not($hideLeft) and $hideRight">top bottom left</xsl:when>
|
||
<xsl:when test="not($hideTop) and not($hideBot) and $hideLeft and not($hideRight)">top bottom right</xsl:when>
|
||
<xsl:when test="not($hideTop) and $hideBot and not($hideLeft) and not($hideRight)">top left right</xsl:when>
|
||
<xsl:when test="$hideTop and not($hideBot) and not($hideLeft) and not($hideRight)">bottom left right</xsl:when>
|
||
|
||
<!-- All four sides visible (or none hidden) -->
|
||
<xsl:when test="not($hideTop or $hideBot or $hideLeft or $hideRight)">box</xsl:when>
|
||
|
||
<!-- Default: roundedbox -->
|
||
<xsl:otherwise>roundedbox</xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:variable>
|
||
|
||
<menclose notation="{$notation}">
|
||
<xsl:apply-templates select="m:e"/>
|
||
</menclose>
|
||
</xsl:template>
|
||
|
||
<!-- Math element base -->
|
||
<xsl:template match="m:e | m:num | m:den | m:sup | m:sub | m:deg | m:fName | m:lim">
|
||
<mrow>
|
||
<xsl:apply-templates select="*"/>
|
||
</mrow>
|
||
</xsl:template>
|
||
|
||
<!-- Template to split mixed operator/delimiter strings character by character -->
|
||
<!-- Recursive template to process text containing | characters -->
|
||
<xsl:template name="process-text-with-pipe">
|
||
<xsl:param name="text"/>
|
||
<xsl:if test="string-length($text) > 0">
|
||
<xsl:choose>
|
||
<!-- If text contains |, split and recurse -->
|
||
<xsl:when test="contains($text, '|')">
|
||
<xsl:variable name="before" select="substring-before($text, '|')"/>
|
||
<xsl:variable name="after" select="substring-after($text, '|')"/>
|
||
<!-- Output before part if not empty -->
|
||
<xsl:if test="$before != ''">
|
||
<xsl:choose>
|
||
<!-- If before is single char, output as mi or mn -->
|
||
<xsl:when test="string-length($before) = 1">
|
||
<xsl:choose>
|
||
<xsl:when test="contains('0123456789', $before)">
|
||
<mn><xsl:value-of select="$before"/></mn>
|
||
</xsl:when>
|
||
<xsl:otherwise>
|
||
<mi><xsl:value-of select="$before"/></mi>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:when>
|
||
<!-- Multi-char - use split-operators -->
|
||
<xsl:otherwise>
|
||
<xsl:call-template name="split-operators">
|
||
<xsl:with-param name="text" select="$before"/>
|
||
</xsl:call-template>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:if>
|
||
<!-- Output | as fence -->
|
||
<mo fence="true">|</mo>
|
||
<!-- Recurse on after part -->
|
||
<xsl:if test="$after != ''">
|
||
<xsl:call-template name="process-text-with-pipe">
|
||
<xsl:with-param name="text" select="$after"/>
|
||
</xsl:call-template>
|
||
</xsl:if>
|
||
</xsl:when>
|
||
<!-- No more | in text - output as-is -->
|
||
<xsl:otherwise>
|
||
<xsl:choose>
|
||
<!-- Single char -->
|
||
<xsl:when test="string-length($text) = 1">
|
||
<xsl:choose>
|
||
<xsl:when test="contains('0123456789', $text)">
|
||
<mn><xsl:value-of select="$text"/></mn>
|
||
</xsl:when>
|
||
<xsl:otherwise>
|
||
<mi><xsl:value-of select="$text"/></mi>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:when>
|
||
<!-- Multi-char - use split-operators for proper handling -->
|
||
<xsl:otherwise>
|
||
<xsl:call-template name="split-operators">
|
||
<xsl:with-param name="text" select="$text"/>
|
||
</xsl:call-template>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
</xsl:if>
|
||
</xsl:template>
|
||
|
||
<xsl:template name="split-operators">
|
||
<xsl:param name="text"/>
|
||
<!-- Style variant to apply to identifier (letter) elements: 'normal', 'bold', 'italic', or empty -->
|
||
<xsl:param name="math_variant" select="''"/>
|
||
<xsl:if test="string-length($text) > 0">
|
||
<!-- Process first character -->
|
||
<xsl:variable name="char" select="substring($text, 1, 1)"/>
|
||
<xsl:choose>
|
||
<!-- Fence delimiters with fence="true" -->
|
||
<xsl:when test="$char = '(' or $char = ')' or $char = '[' or $char = ']' or $char = '|'">
|
||
<mo fence="true"><xsl:value-of select="$char"/></mo>
|
||
</xsl:when>
|
||
<!-- Plain operators (no fence attribute) -->
|
||
<!-- NOTE: { and } are converted to markers to distinguish source braces from generated LaTeX braces -->
|
||
<xsl:when test="$char = '+' or $char = '-' or $char = '=' or $char = '<' or $char = '>' or $char = '*' or $char = '/' or $char = '±' or $char = '≠' or $char = '≤' or $char = '≥' or $char = '×' or $char = '÷'">
|
||
<mo><xsl:value-of select="$char"/></mo>
|
||
</xsl:when>
|
||
<!-- Source braces - use markers -->
|
||
<xsl:when test="$char = '{'">
|
||
<mo>__LBRACE__</mo>
|
||
</xsl:when>
|
||
<xsl:when test="$char = '}'">
|
||
<mo>__RBRACE__</mo>
|
||
</xsl:when>
|
||
<!-- Numbers -->
|
||
<xsl:when test="contains('0123456789', $char)">
|
||
<mn><xsl:value-of select="$char"/></mn>
|
||
</xsl:when>
|
||
<!-- Identifiers (letters) -->
|
||
<xsl:otherwise>
|
||
<mi>
|
||
<!-- Apply mathvariant from parent run style if available -->
|
||
<xsl:if test="$math_variant != ''">
|
||
<xsl:attribute name="mathvariant"><xsl:value-of select="$math_variant"/></xsl:attribute>
|
||
</xsl:if>
|
||
<xsl:value-of select="$char"/>
|
||
</mi>
|
||
</xsl:otherwise>
|
||
</xsl:choose>
|
||
<!-- Recursively process remaining characters -->
|
||
<xsl:if test="string-length($text) > 1">
|
||
<xsl:call-template name="split-operators">
|
||
<xsl:with-param name="text" select="substring($text, 2)"/>
|
||
<xsl:with-param name="math_variant" select="$math_variant"/>
|
||
</xsl:call-template>
|
||
</xsl:if>
|
||
</xsl:if>
|
||
</xsl:template>
|
||
|
||
<!-- Properties elements - ignore -->
|
||
<xsl:template match="m:rPr | m:ctrlPr | m:dPr | m:fPr | m:funcPr | m:limLowPr | m:limUppPr | m:naryPr | m:phantPr | m:radPr | m:sPrePr | m:sSubPr | m:sSubSupPr | m:sSupPr | m:accPr | m:barPr | m:boxPr | m:borderBoxPr | m:dPr | m:eqArrPr | m:groupChrPr | m:mPr | w:rPr">
|
||
<!-- Ignore formatting properties -->
|
||
</xsl:template>
|
||
|
||
<!-- Control properties - ignore -->
|
||
<xsl:template match="m:ctrlPr">
|
||
<!-- Ignore control properties -->
|
||
</xsl:template>
|
||
|
||
<!-- Default template -->
|
||
<xsl:template match="*">
|
||
<xsl:apply-templates select="*"/>
|
||
</xsl:template>
|
||
|
||
</xsl:stylesheet> |