[
  {
    "path": ".gitattributes",
    "content": "#\n# https://help.github.com/articles/dealing-with-line-endings/\n#\n# These are explicitly windows files and should use crlf\n*.bat           text eol=crlf\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**HTML content:**\n<p>Some html content to reproduce the issue</p>\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**poi-tl-ext version:**\n[e.g. 0.3.12]\n\n**poi-tl version:**\n[e.g. 1.9.1]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled class file\n*.class\n\n# Log file\n*.log\n\n# BlueJ files\n*.ctxt\n\n# Mobile Tools for Java (J2ME)\n.mtj.tmp/\n\n# Package Files #\n*.jar\n*.war\n*.nar\n*.ear\n*.zip\n*.tar.gz\n*.rar\n/*.docx\n\n### STS ###\n.apt_generated\n.classpath\n.factorypath\n.project\n.settings\n.springBeans\n\n### IntelliJ IDEA ###\n.idea/\n*.iws\n*.iml\n*.ipr\n\n### NetBeans ###\nnbproject/private/\n!dev/build/*\nbuild/*\nnbbuild/\ndist/\nnbdist/\n.nb-gradle/\n\n# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\n\n# Ignore Gradle project-specific cache directory\n.gradle/\ngradle.properties\n\n# Ignore Gradle build output directory\nbuild/\nout/\n.gradletasknamecache\nsecring.gpg\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# poi-tl-ext\n![GitHub](https://img.shields.io/github/license/draco1023/poi-tl-ext) ![JDK](https://img.shields.io/badge/jdk-1.8-blue)\n\n# Maven\n\npoi 4.x poi-tl 1.11 以前的版本\n\n```xml\n<dependency>\n    <groupId>io.github.draco1023</groupId>\n    <artifactId>poi-tl-ext</artifactId>\n    <version>0.4.26</version>\n</dependency>\n```\n\npoi 5.x poi-tl 1.11.0+\n\n```xml\n<dependency>\n    <groupId>io.github.draco1023</groupId>\n    <artifactId>poi-tl-ext</artifactId>\n    <version>0.4.26-poi5</version>\n</dependency>\n```\n\n# 扩展功能\n\n在 [poi-tl](https://github.com/Sayi/poi-tl) 的基础上扩展了如下功能：\n\n- 支持渲染`HTML`字符串，插件`HtmlRenderPolicy`的使用方法如下（也可参考[文档](http://deepoove.com/poi-tl/#_%E4%BD%BF%E7%94%A8%E6%8F%92%E4%BB%B6)）\n\n  ```java\n  HtmlRenderPolicy htmlRenderPolicy = new HtmlRenderPolicy();\n  Configure configure = Configure.builder()\n          .bind(\"key\", htmlRenderPolicy)\n          .build();\n  Map<String, Object> data = new HashMap<>();\n  data.put(\"key\", \"<p>Hello <b>world</b>!</p>\");\n  XWPFTemplate.compile(\"input.docx\", configure).render(data).writeToFile(\"output.docx\");\n  ```\n  \n  `HtmlRenderPolicy`可以通过`HtmlRenderConfig`进行如下设置：\n  - `globalFont` 全局默认字体（用于归一化处理，而不是用于样式兜底）\n  - `globalFontSize` 全局默认字号（用于归一化处理，而不是用于样式兜底）\n  - `showDefaultTableBorderInTableCell` 是否显示嵌套表格的边框（`poi`生成嵌套表格时默认不显示边框，见[#12](https://github.com/draco1023/poi-tl-ext/issues/12)）\n  - `numberingIndent` 多级列表项缩进长度，默认值360\n  - `numberingHanging` 列表项悬挂长度，默认值360，CSS样式`list-style-position`为`inside`时该参数无效\n  - `numberingSpacing` 列表编号与内容之间的间隔类型，`STLevelSuffix.NOTHING`/`STLevelSuffix.SPACE`/`STLevelSuffix.TAB`\n  \n  自定义`<latex>`标签，允许渲染嵌入在`HTML`中的`LaTeX`，字符串格式可参考[文档](https://www2.ph.ed.ac.uk/snuggletex/documentation/supported-latex.html)。\n  \n  _目前实现了富文本编辑器可实现的大部分效果，后续继续改进..._\n\n- 支持渲染`MathML`字符串，插件类为`MathMLRenderPolicy`\n- 支持渲染`LaTeX`字符串，插件类为`LaTeXRenderPolicy`\n\n## 支持我\n如果您觉得这个插件节省了您的时间和精力，或者解决了您的难题，可以考虑支持一下我的工作，感谢！ ⚡⚡⚡\n\n![wechat_sp](https://pub-1085551b511e4719a277177fe3c8f95b.r2.dev/wechat_sp.jpg)"
  },
  {
    "path": "build.gradle",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nplugins {\n    id 'java-library'\n    id 'io.github.sgtsilvio.gradle.maven-central-publishing' version '0.4.0'\n}\n\ndef getBranchName() {\n    providers.exec {\n        commandLine(\"git\", \"branch\", \"--show-current\")\n    }.standardOutput.asText.get().trim()\n}\n\next {\n    VERSION_NAME = 'poi-5' == getBranchName()? \"${project.V}-poi5\" : project.V\n}\n\ngroup = GROUP\nversion = VERSION_NAME\n\nrepositories {\n    mavenLocal()\n    jcenter()\n    mavenCentral()\n}\n\ndependencies {\n    api 'com.deepoove:poi-tl:1.9.1'\n    api 'org.apache.poi:ooxml-schemas:1.4'\n    api 'org.apache.commons:commons-lang3:3.10'\n    api 'commons-io:commons-io:2.11.0'\n    implementation 'net.sourceforge.cssparser:cssparser:0.9.29'\n    implementation 'org.jsoup:jsoup:1.15.3'\n    implementation 'net.sf.saxon:Saxon-HE:11.4'\n    implementation 'de.rototor.snuggletex:snuggletex-core:1.3.0'\n    implementation 'com.drewnoakes:metadata-extractor:2.19.0'\n    implementation 'com.twelvemonkeys.imageio:imageio-batik:3.10.1'\n    implementation 'org.apache.xmlgraphics:batik-rasterizer-ext:1.17'\n    implementation 'com.twelvemonkeys.imageio:imageio-webp:3.10.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-pict:3.10.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-tiff:3.10.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-jpeg:3.10.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-bmp:3.10.1'\n\n    compileOnly 'com.google.code.findbugs:jsr305:3.0.2'\n\n    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2'\n    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.2'\n    testRuntimeOnly 'org.slf4j:slf4j-simple:1.7.7'\n}\n\nsourceCompatibility = JavaVersion.VERSION_1_8\ntargetCompatibility = JavaVersion.VERSION_1_8\n\ntest {\n    useJUnitPlatform()\n}\n\ntasks.withType(JavaCompile) {\n    options.encoding = 'UTF-8'\n    options.fork = true\n}\n\njavadoc {\n    options {\n        encoding 'UTF-8'\n        charSet 'UTF-8'\n    }\n}\njavadoc.options.addBooleanOption('Xdoclint:none', true)\n\njava {\n    withSourcesJar()\n    withJavadocJar()\n}\n\npublishing {\n    publications {\n        maven(MavenPublication) {\n            from components.java\n            groupId = GROUP\n            artifactId = POM_ARTIFACT_ID\n            version = VERSION_NAME\n            pom {\n                name = POM_NAME\n                description = POM_DESCRIPTION\n                url = POM_URL\n                licenses {\n                    license {\n                        name = POM_LICENCE_NAME\n                        url = POM_LICENCE_URL\n                    }\n                }\n                developers {\n                    developer {\n                        id = POM_DEVELOPER_ID\n                        name = POM_DEVELOPER_NAME\n                        url = POM_DEVELOPER_URL\n                    }\n                }\n                scm {\n                    connection = POM_SCM_CONNECTION\n                    developerConnection = POM_SCM_DEV_CONNECTION\n                    url = POM_SCM_URL\n                }\n            }\n        }\n    }\n}\n\nsigning {\n    sign publishing.publications.maven\n}"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#\n# Copyright 2016 - 2021 Draco, https://github.com/draco1023\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.6.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Dfile.encoding=UTF-8\" \"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    \n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Dfile.encoding=UTF-8\" \"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "settings.gradle",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nrootProject.name = 'poi-tl-ext'\n"
  },
  {
    "path": "src/main/java/org/apache/poi/xwpf/usermodel/SVGPictureData.java",
    "content": "package org.apache.poi.xwpf.usermodel;\n\nimport org.apache.poi.openxml4j.opc.PackagePart;\n\n/**\n * SVGPictureData\n *\n * @author Draco\n * @since 2022-04-12\n */\npublic class SVGPictureData extends XWPFPictureData {\n\n    public static final int PICTURE_TYPE_SVG = 1;\n\n    public SVGPictureData() {\n    }\n\n    public SVGPictureData(PackagePart part) {\n        super(part);\n    }\n\n    public static void initRelation() {\n        RELATIONS[PICTURE_TYPE_SVG] = SVGRelation.INSTANCE;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/apache/poi/xwpf/usermodel/SVGRelation.java",
    "content": "package org.apache.poi.xwpf.usermodel;\n\nimport org.apache.poi.ooxml.POIXMLRelation;\nimport org.apache.poi.openxml4j.opc.PackageRelationshipTypes;\nimport org.apache.poi.sl.usermodel.PictureData;\n\nimport javax.xml.namespace.QName;\n\n/**\n * SVGRelation\n *\n * @author Draco\n * @since 2022-04-12\n */\npublic class SVGRelation extends POIXMLRelation {\n    public static final SVGRelation INSTANCE = new SVGRelation();\n    /**\n     * @see org.apache.poi.xslf.usermodel.XSLFPictureShape#MS_SVG_NS\n     */\n    public static final String MS_SVG_NS = \"http://schemas.microsoft.com/office/drawing/2016/SVG/main\";\n    /**\n     * @see org.apache.poi.xslf.usermodel.XSLFPictureShape#SVG_URI\n     */\n    public static final String SVG_URI = \"{96DAC541-7B7A-43D3-8B79-37D633B846F1}\";\n    /**\n     * @see org.apache.poi.xslf.usermodel.XSLFPictureShape#EMBED_TAG\n     */\n    public static final QName EMBED_TAG = new QName(PackageRelationshipTypes.CORE_PROPERTIES_ECMA376_NS,\n            \"embed\", \"r\");\n\n    public static final String SVG_BLIP = \"svgBlip\";\n\n    public static final String SVG_PREFIX = \"asvg\";\n\n    public static final QName SVG_QNAME = new QName(MS_SVG_NS, SVG_BLIP, SVG_PREFIX);\n\n    /**\n     * @see XWPFRelation#IMAGE_PNG\n     */\n    private SVGRelation() {\n        super(PictureData.PictureType.SVG.contentType,\n                PackageRelationshipTypes.IMAGE_PART,\n                \"/word/media/image#.svg\",\n                SVGPictureData::new, SVGPictureData::new, null);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/ImageInfo.java",
    "content": "package org.ddr.image;\n\nimport java.awt.*;\nimport java.io.ByteArrayInputStream;\n\npublic class ImageInfo {\n    private ByteArrayInputStream stream;\n    private ImageType type;\n    private Dimension dimension;\n\n    public ImageInfo(ByteArrayInputStream stream) {\n        this.stream = stream;\n    }\n\n    public ImageInfo(ByteArrayInputStream stream, ImageType type, Dimension dimension) {\n        this.stream = stream;\n        this.type = type;\n        this.dimension = dimension;\n    }\n\n    public ByteArrayInputStream getStream() {\n        return stream;\n    }\n\n    public void setStream(ByteArrayInputStream stream) {\n        this.stream = stream;\n    }\n\n    public ImageType getType() {\n        return type;\n    }\n\n    public void setType(ImageType type) {\n        this.type = type;\n    }\n\n    public Dimension getDimension() {\n        return dimension;\n    }\n\n    public void setDimension(Dimension dimension) {\n        this.dimension = dimension;\n    }\n\n    public int getWidth() {\n        return dimension == null ? 0 : dimension.width;\n    }\n\n    public int getHeight() {\n        return dimension == null ? 0 : dimension.height;\n    }\n\n    public int getRawType() {\n        return type == null ? -1 : type.getType();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/ImageInputStreamWrapper.java",
    "content": "package org.ddr.image;\n\nimport javax.imageio.stream.IIOByteBuffer;\nimport javax.imageio.stream.ImageInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteOrder;\n\npublic class ImageInputStreamWrapper extends InputStream implements ImageInputStream {\n\n    private final ImageInputStream input;\n\n    public ImageInputStreamWrapper(ImageInputStream input) {\n        this.input = input;\n    }\n\n    @Override\n    public void setByteOrder(ByteOrder byteOrder) {\n        input.setByteOrder(byteOrder);\n    }\n\n    @Override\n    public ByteOrder getByteOrder() {\n        return input.getByteOrder();\n    }\n\n    @Override\n    public int read() throws IOException {\n        return input.read();\n    }\n\n    @Override\n    public void readBytes(IIOByteBuffer buf, int len) throws IOException {\n        input.readBytes(buf, len);\n    }\n\n    @Override\n    public boolean readBoolean() throws IOException {\n        return input.readBoolean();\n    }\n\n    @Override\n    public byte readByte() throws IOException {\n        return input.readByte();\n    }\n\n    @Override\n    public int readUnsignedByte() throws IOException {\n        return input.readUnsignedByte();\n    }\n\n    @Override\n    public short readShort() throws IOException {\n        return input.readShort();\n    }\n\n    @Override\n    public int readUnsignedShort() throws IOException {\n        return input.readUnsignedShort();\n    }\n\n    @Override\n    public char readChar() throws IOException {\n        return input.readChar();\n    }\n\n    @Override\n    public int readInt() throws IOException {\n        return input.readInt();\n    }\n\n    @Override\n    public long readUnsignedInt() throws IOException {\n        return input.readUnsignedInt();\n    }\n\n    @Override\n    public long readLong() throws IOException {\n        return input.readLong();\n    }\n\n    @Override\n    public float readFloat() throws IOException {\n        return input.readFloat();\n    }\n\n    @Override\n    public double readDouble() throws IOException {\n        return input.readDouble();\n    }\n\n    @Override\n    public String readLine() throws IOException {\n        return input.readLine();\n    }\n\n    @Override\n    public String readUTF() throws IOException {\n        return input.readUTF();\n    }\n\n    @Override\n    public void readFully(byte[] b, int off, int len) throws IOException {\n        input.readFully(b, off, len);\n    }\n\n    @Override\n    public void readFully(byte[] b) throws IOException {\n        input.readFully(b);\n    }\n\n    @Override\n    public void readFully(short[] s, int off, int len) throws IOException {\n        input.readFully(s, off, len);\n    }\n\n    @Override\n    public void readFully(char[] c, int off, int len) throws IOException {\n        input.readFully(c, off, len);\n    }\n\n    @Override\n    public void readFully(int[] i, int off, int len) throws IOException {\n        input.readFully(i, off, len);\n    }\n\n    @Override\n    public void readFully(long[] l, int off, int len) throws IOException {\n        input.readFully(l, off, len);\n    }\n\n    @Override\n    public void readFully(float[] f, int off, int len) throws IOException {\n        input.readFully(f, off, len);\n    }\n\n    @Override\n    public void readFully(double[] d, int off, int len) throws IOException {\n        input.readFully(d, off, len);\n    }\n\n    @Override\n    public long getStreamPosition() throws IOException {\n        return input.getStreamPosition();\n    }\n\n    @Override\n    public int getBitOffset() throws IOException {\n        return input.getBitOffset();\n    }\n\n    @Override\n    public void setBitOffset(int bitOffset) throws IOException {\n        input.setBitOffset(bitOffset);\n    }\n\n    @Override\n    public int readBit() throws IOException {\n        return input.readBit();\n    }\n\n    @Override\n    public long readBits(int numBits) throws IOException {\n        return input.readBits(numBits);\n    }\n\n    @Override\n    public long length() throws IOException {\n        return input.length();\n    }\n\n    @Override\n    public int skipBytes(int n) throws IOException {\n        return input.skipBytes(n);\n    }\n\n    @Override\n    public long skipBytes(long n) throws IOException {\n        return input.skipBytes(n);\n    }\n\n    @Override\n    public void seek(long pos) throws IOException {\n        input.seek(pos);\n    }\n\n    @Override\n    public void mark() {\n        input.mark();\n    }\n\n    @Override\n    public void flushBefore(long pos) throws IOException {\n        input.flushBefore(pos);\n    }\n\n    @Override\n    public void flush() throws IOException {\n        input.flush();\n    }\n\n    @Override\n    public long getFlushedPosition() {\n        return input.getFlushedPosition();\n    }\n\n    @Override\n    public boolean isCached() {\n        return input.isCached();\n    }\n\n    @Override\n    public boolean isCachedMemory() {\n        return input.isCachedMemory();\n    }\n\n    @Override\n    public boolean isCachedFile() {\n        return input.isCachedFile();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/ImageType.java",
    "content": "package org.ddr.image;\n\nimport org.apache.poi.xwpf.usermodel.Document;\n\npublic enum ImageType {\n    EMF(Document.PICTURE_TYPE_EMF),\n    WMF(Document.PICTURE_TYPE_WMF),\n    PICT(Document.PICTURE_TYPE_PICT),\n    JPEG(Document.PICTURE_TYPE_JPEG),\n    JPG(Document.PICTURE_TYPE_JPEG),\n    PNG(Document.PICTURE_TYPE_PNG),\n    DIB(Document.PICTURE_TYPE_DIB),\n    GIF(Document.PICTURE_TYPE_GIF),\n    TIF(Document.PICTURE_TYPE_TIFF),\n    TIFF(Document.PICTURE_TYPE_TIFF),\n    EPS(Document.PICTURE_TYPE_EPS),\n    BMP(Document.PICTURE_TYPE_BMP),\n    WPG(Document.PICTURE_TYPE_WPG);\n\n    private final int type;\n\n    ImageType(int type) {\n        this.type = type;\n    }\n\n    public String getExtension() {\n        return name().toLowerCase();\n    }\n\n    public int getType() {\n        return type;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/MetadataReader.java",
    "content": "package org.ddr.image;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Metadata;\n\nimport java.awt.*;\n\npublic interface MetadataReader {\n    boolean canRead(FileType type);\n\n    ImageType getType(Metadata metadata);\n\n    Dimension getDimension(Metadata metadata);\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/MetadataReaders.java",
    "content": "package org.ddr.image;\n\nimport org.ddr.image.avif.AvifMetadataReader;\nimport org.ddr.image.bmp.BmpMetadataReader;\nimport org.ddr.image.eps.EpsMetadataReader;\nimport org.ddr.image.gif.GifMetadataReader;\nimport org.ddr.image.heif.HeifMetadataReader;\nimport org.ddr.image.jpeg.JpegMetadataReader;\nimport org.ddr.image.png.PngMetadataReader;\nimport org.ddr.image.tiff.TiffMetadataReader;\nimport org.ddr.image.webp.WebpMetadataReader;\n\npublic class MetadataReaders {\n    public static final MetadataReader[] INSTANCES = {\n            new JpegMetadataReader(),\n            new PngMetadataReader(),\n            new GifMetadataReader(),\n            new WebpMetadataReader(),\n            new AvifMetadataReader(),\n            new HeifMetadataReader(),\n            new BmpMetadataReader(),\n            new TiffMetadataReader(),\n            new EpsMetadataReader()\n    };\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/avif/AvifImageReader.java",
    "content": "package org.ddr.image.avif;\n\nimport org.ddr.image.heif.HeifImageReader;\n\nimport javax.imageio.spi.ImageReaderSpi;\n\npublic class AvifImageReader extends HeifImageReader {\n\n    public AvifImageReader(ImageReaderSpi provider) {\n        super(provider, new AvifMetadataReader(), \"avif\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/avif/AvifImageReaderSpi.java",
    "content": "package org.ddr.image.avif;\n\nimport com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;\n\nimport javax.imageio.ImageReader;\nimport javax.imageio.stream.ImageInputStream;\nimport java.io.IOException;\nimport java.util.HashSet;\nimport java.util.Locale;\nimport java.util.Set;\n\n\npublic final class AvifImageReaderSpi extends ImageReaderSpiBase {\n    private static final Set<String> TYPES;\n\n    static {\n        TYPES = new HashSet<>(4);\n//        TYPES.add(\"mif1\");\n//        TYPES.add(\"msf1\");\n        TYPES.add(\"miaf\");\n        TYPES.add(\"avif\");\n        TYPES.add(\"avis\");\n        TYPES.add(\"avio\");\n    }\n    public AvifImageReaderSpi() {\n        super(new AvifProviderInfo());\n    }\n\n    @Override\n    public boolean canDecodeInput(Object source) throws IOException {\n        return source instanceof ImageInputStream && canDecode((ImageInputStream) source);\n    }\n\n    private boolean canDecode(ImageInputStream input) throws IOException {\n        try {\n            input.mark();\n            for (int i = 0; i < 4; i++) {\n                input.read();\n            }\n            if (input.read() == 'f' && input.read() == 't' && input.read() == 'y' && input.read() == 'p') {\n                byte[] bytes = new byte[4];\n                int length = input.read(bytes);\n                if (length == 4) {\n                    String s = new String(bytes);\n                    return TYPES.contains(s);\n                }\n            }\n        } catch (Exception ignored) {\n        } finally {\n            input.reset();\n        }\n        return false;\n    }\n\n    @Override\n    public ImageReader createReaderInstance(Object extension) throws IOException {\n        return new AvifImageReader(this);\n    }\n\n    @Override\n    public String getDescription(Locale locale) {\n        return \"AV1 Image File (AVIF) format image reader\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/avif/AvifMetadataReader.java",
    "content": "package org.ddr.image.avif;\n\nimport com.drew.imaging.FileType;\nimport org.ddr.image.heif.HeifMetadataReader;\n\npublic class AvifMetadataReader extends HeifMetadataReader {\n    @Override\n    public boolean canRead(FileType type) {\n        // FIXME metadata-extractor 一直未发版支持 AVIF 格式，会被归为 QuickTime 格式\n        return type == FileType.QuickTime || type == FileType.Heif;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/avif/AvifProviderInfo.java",
    "content": "package org.ddr.image.avif;\n\nimport com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;\n\nfinal class AvifProviderInfo extends ReaderWriterProviderInfo {\n    AvifProviderInfo() {\n        super(\n                AvifProviderInfo.class,\n                new String[]{\"avif\", \"AVIF\"}, // Names\n                new String[]{\"avif\", \"avifs\"}, // Suffixes\n                new String[]{\"image/avif\", \"image/avifs\"}, // Mime-types\n                \"org.ddr.image.avif.AvifImageReader\", // Reader class name\n                new String[]{\"org.ddr.image.avif.AvifImageReaderSpi\"},\n                null,\n                null,\n                false, null, null, null, null,\n                true, null, null, null, null\n        );\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/bmp/BmpMetadataReader.java",
    "content": "package org.ddr.image.bmp;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Directory;\nimport com.drew.metadata.Metadata;\nimport com.drew.metadata.bmp.BmpHeaderDirectory;\nimport org.ddr.image.ImageType;\nimport org.ddr.image.MetadataReader;\n\nimport java.awt.*;\n\npublic class BmpMetadataReader implements MetadataReader {\n    @Override\n    public boolean canRead(FileType type) {\n        return type == FileType.Bmp;\n    }\n\n    @Override\n    public ImageType getType(Metadata metadata) {\n        return ImageType.BMP;\n    }\n\n    @Override\n    public Dimension getDimension(Metadata metadata) {\n        for (Directory directory : metadata.getDirectories()) {\n            if (directory instanceof BmpHeaderDirectory) {\n                Integer width = directory.getInteger(BmpHeaderDirectory.TAG_IMAGE_WIDTH);\n                Integer height = directory.getInteger(BmpHeaderDirectory.TAG_IMAGE_HEIGHT);\n                if (width != null && height != null) {\n                    return new Dimension(width, height);\n                }\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/eps/EpsMetadataReader.java",
    "content": "package org.ddr.image.eps;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Directory;\nimport com.drew.metadata.Metadata;\nimport com.drew.metadata.eps.EpsDirectory;\nimport org.ddr.image.ImageType;\nimport org.ddr.image.MetadataReader;\n\nimport java.awt.*;\n\npublic class EpsMetadataReader implements MetadataReader{\n    @Override\n    public boolean canRead(FileType type) {\n        return type == FileType.Eps;\n    }\n\n    @Override\n    public ImageType getType(Metadata metadata) {\n        return ImageType.EPS;\n    }\n\n    @Override\n    public Dimension getDimension(Metadata metadata) {\n        for (Directory directory : metadata.getDirectories()) {\n            if (directory instanceof EpsDirectory) {\n                Integer width = directory.getInteger(EpsDirectory.TAG_IMAGE_WIDTH);\n                Integer height = directory.getInteger(EpsDirectory.TAG_IMAGE_HEIGHT);\n                if (width != null && height != null) {\n                    return new Dimension(width, height);\n                }\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/gif/GifMetadataReader.java",
    "content": "package org.ddr.image.gif;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Directory;\nimport com.drew.metadata.Metadata;\nimport com.drew.metadata.gif.GifHeaderDirectory;\nimport org.ddr.image.ImageType;\nimport org.ddr.image.MetadataReader;\n\nimport java.awt.*;\n\npublic class GifMetadataReader implements MetadataReader{\n    @Override\n    public boolean canRead(FileType type) {\n        return type == FileType.Gif;\n    }\n\n    @Override\n    public ImageType getType(Metadata metadata) {\n        return ImageType.GIF;\n    }\n\n    @Override\n    public Dimension getDimension(Metadata metadata) {\n        for (Directory directory : metadata.getDirectories()) {\n            if (directory instanceof GifHeaderDirectory) {\n                Integer width = directory.getInteger(GifHeaderDirectory.TAG_IMAGE_WIDTH);\n                Integer height = directory.getInteger(GifHeaderDirectory.TAG_IMAGE_HEIGHT);\n                if (width != null && height != null) {\n                    return new Dimension(width, height);\n                }\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/heif/HeifImageReader.java",
    "content": "package org.ddr.image.heif;\n\nimport com.drew.imaging.FileType;\nimport com.drew.imaging.ImageMetadataReader;\nimport com.drew.imaging.ImageProcessingException;\nimport com.drew.metadata.Metadata;\nimport com.twelvemonkeys.imageio.ImageReaderBase;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.ddr.image.ImageInputStreamWrapper;\nimport org.ddr.image.MetadataReader;\nimport org.ddr.poi.util.HttpURLConnectionUtils;\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Element;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.imageio.ImageIO;\nimport javax.imageio.ImageReadParam;\nimport javax.imageio.ImageTypeSpecifier;\nimport javax.imageio.spi.ImageReaderSpi;\nimport java.awt.*;\nimport java.awt.image.BufferedImage;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.HttpURLConnection;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Iterator;\n\npublic class HeifImageReader extends ImageReaderBase {\n    private static final Logger log = LoggerFactory.getLogger(HeifImageReader.class);\n    protected final MetadataReader metadataReader;\n\n    protected ImageInputStreamWrapper wrapper;\n    protected Metadata metadata;\n    protected Dimension dimension;\n    protected String format;\n\n    public HeifImageReader(ImageReaderSpi provider) {\n        this(provider, new HeifMetadataReader(), \"heic\");\n    }\n\n    protected HeifImageReader(ImageReaderSpi provider, MetadataReader metadataReader, String format) {\n        super(provider);\n        this.metadataReader = metadataReader;\n        this.format = format;\n    }\n\n    @Override\n    protected void resetMembers() {\n        metadata = null;\n        dimension = null;\n    }\n\n    @Override\n    public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {\n        super.setInput(input, seekForwardOnly, ignoreMetadata);\n\n        if (imageInput != null) {\n            wrapper = new ImageInputStreamWrapper(imageInput);\n            try {\n                metadata = ImageMetadataReader.readMetadata(wrapper, 0, FileType.Heif);\n                dimension = metadataReader.getDimension(metadata);\n            } catch (IOException | ImageProcessingException e) {\n                log.warn(\"Failed to read metadata\", e);\n            }\n        }\n    }\n\n    @Override\n    public int getWidth(int imageIndex) throws IOException {\n        return dimension == null ? 0 : dimension.width;\n    }\n\n    @Override\n    public int getHeight(int imageIndex) throws IOException {\n        return dimension == null ? 0 : dimension.height;\n    }\n\n    @Override\n    public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {\n        return null;\n    }\n\n    @Override\n    public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {\n        return convert(param);\n    }\n\n    BufferedImage convert(ImageReadParam param) {\n        HttpURLConnection uploadConnection = null;\n        HttpURLConnection convertConnection = null;\n        HttpURLConnection downloadConnection = null;\n\n        try {\n            uploadConnection = HttpURLConnectionUtils.connect(\"https://ezgif.com/\" + format + \"-to-jpg\");\n            uploadConnection.setInstanceFollowRedirects(false);\n            HttpURLConnectionUtils.initUserAgent(uploadConnection);\n            uploadConnection.setRequestProperty(\"Referer\", \"https://ezgif.com/\" + format + \"-to-jpg\");\n            String boundary = HttpURLConnectionUtils.initFormData(uploadConnection);\n\n            try (OutputStream outputStream = uploadConnection.getOutputStream()) {\n                byte[] boundaryBytes = (\"--\" + boundary).getBytes();\n                wrapper.seek(0);\n                HttpURLConnectionUtils.addFormData(outputStream, boundaryBytes, \"new-image\", \"some.\" + format, wrapper);\n\n                outputStream.write(boundaryBytes);\n                outputStream.write(\"--\".getBytes());\n                outputStream.write(HttpURLConnectionUtils.newLineBytes);\n                outputStream.flush();\n            }\n\n            // 获取上传响应\n            int uploadResponseCode = uploadConnection.getResponseCode();\n            if (uploadResponseCode == HttpURLConnection.HTTP_MOVED_TEMP) {\n                String location = uploadConnection.getHeaderField(\"Location\");\n                String convertUrl = StringUtils.substringBeforeLast(location, \".\");\n                String fileId = StringUtils.substringAfterLast(convertUrl, \"/\");\n                convertUrl += \"?ajax=true\";\n\n                if (log.isDebugEnabled()) {\n                    log.debug(\"{} uploaded: {}\", format, fileId);\n                }\n                convertConnection = HttpURLConnectionUtils.connect(convertUrl);\n                HttpURLConnectionUtils.initUserAgent(convertConnection);\n                convertConnection.setRequestProperty(\"Referer\", location);\n                boundary = HttpURLConnectionUtils.initFormData(convertConnection);\n                try (OutputStream convertOutput = convertConnection.getOutputStream()) {\n                    byte[] boundaryBytes = (\"--\" + boundary).getBytes();\n\n                    HttpURLConnectionUtils.addFormData(convertOutput, boundaryBytes, \"file\", fileId, null);\n                    HttpURLConnectionUtils.addFormData(convertOutput, boundaryBytes, \"percentage\", \"90\", null);\n                    HttpURLConnectionUtils.addFormData(convertOutput, boundaryBytes, \"percentager\", \"90\", null);\n                    HttpURLConnectionUtils.addFormData(convertOutput, boundaryBytes, \"background\", \"#ffffff\", null);\n                    HttpURLConnectionUtils.addFormData(convertOutput, boundaryBytes, \"backgroundc\", \"#ffffff\", null);\n                    HttpURLConnectionUtils.addFormData(convertOutput, boundaryBytes, \"ajax\", \"true\", null);\n\n                    convertOutput.write(boundaryBytes);\n                    convertOutput.write(\"--\".getBytes());\n                    convertOutput.write(HttpURLConnectionUtils.newLineBytes);\n                    convertOutput.flush();\n                }\n                int convertResponseCode = convertConnection.getResponseCode();\n                if (convertResponseCode == HttpURLConnection.HTTP_OK) {\n                    try (InputStream convertResponse = convertConnection.getInputStream()) {\n                        Element body = Jsoup.parse(convertResponse, StandardCharsets.UTF_8.name(), \"\").body();\n                        if (log.isDebugEnabled()) {\n                            log.debug(\"{} converted: {}\", format, body.html());\n                        }\n                        for (Element img : body.select(\"img\")) {\n                            String src = img.attr(\"src\");\n                            if (StringUtils.contains(src, \"ezgif\")) {\n                                String url = \"https:\" + src;\n                                downloadConnection = HttpURLConnectionUtils.connect(url);\n                                HttpURLConnectionUtils.initUserAgent(downloadConnection);\n                                try (InputStream downloadResponse = downloadConnection.getInputStream()) {\n                                    return ImageIO.read(downloadResponse);\n                                }\n                            }\n                        }\n                    }\n                } else {\n                    log.warn(\"Failed to convert {} image. Response code: {}\", format, convertResponseCode);\n                }\n            } else {\n                log.warn(\"Failed to upload image. Response code: {}\", uploadResponseCode);\n            }\n\n        } catch (Exception e) {\n            log.warn(\"Failed to convert {} image\", format, e);\n            IOUtils.close(uploadConnection);\n            IOUtils.close(convertConnection);\n            IOUtils.close(downloadConnection);\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/heif/HeifImageReaderSpi.java",
    "content": "package org.ddr.image.heif;\n\nimport com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;\n\nimport javax.imageio.ImageReader;\nimport javax.imageio.stream.ImageInputStream;\nimport java.io.IOException;\nimport java.util.HashSet;\nimport java.util.Locale;\nimport java.util.Set;\n\n\npublic final class HeifImageReaderSpi extends ImageReaderSpiBase {\n    private static final Set<String> TYPES;\n\n    static {\n        TYPES = new HashSet<>(8);\n        TYPES.add(\"mif1\");\n        TYPES.add(\"msf1\");\n        TYPES.add(\"heic\");\n        TYPES.add(\"heix\");\n        TYPES.add(\"hevc\");\n        TYPES.add(\"hevx\");\n    }\n    public HeifImageReaderSpi() {\n        super(new HeifProviderInfo());\n    }\n\n    @Override\n    public boolean canDecodeInput(Object source) throws IOException {\n        return source instanceof ImageInputStream && canDecode((ImageInputStream) source);\n    }\n\n    private boolean canDecode(ImageInputStream input) throws IOException {\n        // https://docs.oracle.com/javase/7/docs/technotes/guides/imageio/spec/extending.fm3.html\n        // https://github.com/strukturag/libheif/blob/e64bb552f5d48fee5daf69c8c2fd59ec3eee0818/libheif/heif.cc#L102\n        // https://devstreaming-cdn.apple.com/videos/wwdc/2017/513fzgbviu23l/513/513_high_efficiency_image_file_format.pdf?dl=1\n        try {\n            input.mark();\n            for (int i = 0; i < 4; i++) {\n                input.read();\n            }\n            if (input.read() == 'f' && input.read() == 't' && input.read() == 'y' && input.read() == 'p') {\n                byte[] bytes = new byte[4];\n                int length = input.read(bytes);\n                if (length == 4) {\n                    String s = new String(bytes);\n                    return TYPES.contains(s);\n                }\n            }\n        } catch (Exception ignored) {\n        } finally {\n            input.reset();\n        }\n        return false;\n    }\n\n    @Override\n    public ImageReader createReaderInstance(Object extension) throws IOException {\n        return new HeifImageReader(this);\n    }\n\n    @Override\n    public String getDescription(Locale locale) {\n        return \"High Efficiency Image File (HEIF) format image reader\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/heif/HeifMetadataReader.java",
    "content": "package org.ddr.image.heif;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Directory;\nimport com.drew.metadata.Metadata;\nimport com.drew.metadata.exif.ExifDescriptorBase;\nimport com.drew.metadata.exif.ExifIFD0Directory;\nimport com.drew.metadata.exif.ExifSubIFDDirectory;\nimport com.drew.metadata.heif.HeifDescriptor;\nimport com.drew.metadata.heif.HeifDirectory;\nimport org.ddr.image.ImageType;\nimport org.ddr.image.MetadataReader;\n\nimport java.awt.*;\n\npublic class HeifMetadataReader implements MetadataReader {\n    @Override\n    public boolean canRead(FileType type) {\n        return type == FileType.Heif;\n    }\n\n    @Override\n    public ImageType getType(Metadata metadata) {\n        // FIXME read icc profile\n        return ImageType.JPG;\n    }\n\n    /**\n     * @see HeifDescriptor#getRotationDescription()\n     * @see ExifDescriptorBase#getOrientationDescription()\n     */\n    @Override\n    public Dimension getDimension(Metadata metadata) {\n        Integer width = null;\n        Integer height = null;\n        Boolean rotated = null;\n        for (Directory directory : metadata.getDirectories()) {\n            if (directory instanceof HeifDirectory) {\n                Integer r = directory.getInteger(HeifDirectory.TAG_IMAGE_ROTATION);\n                if (r != null) {\n                    rotated = r % 2 == 1;\n                }\n                // FIXME 实际上可以在此处获取到 width 和 height，但是图片会直接嵌入到 word 中，能否呈现因系统支持而异，特意不获取走格式转换流程\n//                if (width == null) {\n//                    width = directory.getInteger(HeifDirectory.TAG_IMAGE_WIDTH);\n//                }\n//                if (height == null) {\n//                    height = directory.getInteger(HeifDirectory.TAG_IMAGE_HEIGHT);\n//                }\n            } else if (directory instanceof ExifIFD0Directory) {\n                if (rotated == null) {\n                    Integer r = directory.getInteger(ExifIFD0Directory.TAG_ORIENTATION);\n                    if (r != null) {\n                        rotated = r > 4;\n                    }\n                }\n            } else if (directory instanceof ExifSubIFDDirectory) {\n                if (width == null) {\n                    width = directory.getInteger(ExifSubIFDDirectory.TAG_EXIF_IMAGE_WIDTH);\n                }\n                if (height == null) {\n                    height = directory.getInteger(ExifSubIFDDirectory.TAG_EXIF_IMAGE_HEIGHT);\n                }\n            }\n        }\n        if (width != null && height != null) {\n            if (Boolean.TRUE.equals(rotated)) {\n                //noinspection SuspiciousNameCombination\n                return new Dimension(height, width);\n            }\n            return new Dimension(width, height);\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/heif/HeifProviderInfo.java",
    "content": "package org.ddr.image.heif;\n\nimport com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;\n\nfinal class HeifProviderInfo extends ReaderWriterProviderInfo {\n    HeifProviderInfo() {\n        super(\n                HeifProviderInfo.class,\n                new String[]{\"heif\", \"HEIF\"}, // Names\n                new String[]{\"heif\", \"heic\"}, // Suffixes\n                new String[]{\"image/heif\", \"image/heic\", \"image/heif-sequence\", \"image/heic-sequence\"}, // Mime-types\n                \"org.ddr.image.heif.HeifImageReader\", // Reader class name\n                new String[]{\"org.ddr.image.heif.HeifImageReaderSpi\"},\n                null,\n                null,\n                false, null, null, null, null,\n                true, null, null, null, null\n        );\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/jpeg/JpegMetadataReader.java",
    "content": "package org.ddr.image.jpeg;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Directory;\nimport com.drew.metadata.Metadata;\nimport com.drew.metadata.jpeg.JpegDirectory;\nimport org.ddr.image.ImageType;\nimport org.ddr.image.MetadataReader;\n\nimport java.awt.*;\n\npublic class JpegMetadataReader implements MetadataReader {\n    @Override\n    public boolean canRead(FileType type) {\n        return type == FileType.Jpeg;\n    }\n\n    @Override\n    public ImageType getType(Metadata metadata) {\n        return ImageType.JPG;\n    }\n\n    @Override\n    public Dimension getDimension(Metadata metadata) {\n        for (Directory directory : metadata.getDirectories()) {\n            if (directory instanceof JpegDirectory) {\n                Integer width = directory.getInteger(JpegDirectory.TAG_IMAGE_WIDTH);\n                Integer height = directory.getInteger(JpegDirectory.TAG_IMAGE_HEIGHT);\n                if (width != null && height != null) {\n                    return new Dimension(width, height);\n                }\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/png/PngMetadataReader.java",
    "content": "package org.ddr.image.png;\n\nimport com.drew.imaging.FileType;\nimport com.drew.imaging.png.PngChunkType;\nimport com.drew.metadata.Directory;\nimport com.drew.metadata.Metadata;\nimport com.drew.metadata.png.PngDirectory;\nimport org.ddr.image.ImageType;\nimport org.ddr.image.MetadataReader;\n\nimport java.awt.*;\n\npublic class PngMetadataReader implements MetadataReader {\n    @Override\n    public boolean canRead(FileType type) {\n        return type == FileType.Png;\n    }\n\n    @Override\n    public ImageType getType(Metadata metadata) {\n        return ImageType.PNG;\n    }\n\n    @Override\n    public Dimension getDimension(Metadata metadata) {\n        for (Directory directory : metadata.getDirectories()) {\n            if (directory instanceof PngDirectory) {\n                if (((PngDirectory) directory).getPngChunkType() == PngChunkType.IHDR) {\n                    Integer width = directory.getInteger(PngDirectory.TAG_IMAGE_WIDTH);\n                    Integer height = directory.getInteger(PngDirectory.TAG_IMAGE_HEIGHT);\n                    if (width != null && height != null) {\n                        return new Dimension(width, height);\n                    }\n                }\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/tiff/TiffMetadataReader.java",
    "content": "package org.ddr.image.tiff;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Directory;\nimport com.drew.metadata.Metadata;\nimport com.drew.metadata.exif.ExifIFD0Directory;\nimport org.ddr.image.ImageType;\nimport org.ddr.image.MetadataReader;\n\nimport java.awt.*;\nimport java.util.EnumSet;\n\npublic class TiffMetadataReader implements MetadataReader {\n    private static final EnumSet<FileType> TIFF_TYPES = EnumSet.of(FileType.Tiff, FileType.Arw, FileType.Cr2, FileType.Nef, FileType.Orf, FileType.Rw2);\n\n    @Override\n    public boolean canRead(FileType type) {\n        return TIFF_TYPES.contains(type);\n    }\n\n    @Override\n    public ImageType getType(Metadata metadata) {\n        return ImageType.TIFF;\n    }\n\n    @Override\n    public Dimension getDimension(Metadata metadata) {\n        for (Directory directory : metadata.getDirectories()) {\n            if (directory instanceof ExifIFD0Directory) {\n                Integer width = directory.getInteger(ExifIFD0Directory.TAG_IMAGE_WIDTH);\n                Integer height = directory.getInteger(ExifIFD0Directory.TAG_IMAGE_HEIGHT);\n                if (width != null && height != null) {\n                    return new Dimension(width, height);\n                }\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/image/webp/WebpMetadataReader.java",
    "content": "package org.ddr.image.webp;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Directory;\nimport com.drew.metadata.Metadata;\nimport com.drew.metadata.webp.WebpDirectory;\nimport org.ddr.image.ImageType;\nimport org.ddr.image.MetadataReader;\n\nimport java.awt.*;\n\npublic class WebpMetadataReader implements MetadataReader{\n    @Override\n    public boolean canRead(FileType type) {\n        return type == FileType.WebP;\n    }\n\n    @Override\n    public ImageType getType(Metadata metadata) {\n        for (Directory directory : metadata.getDirectories()) {\n            if (directory instanceof WebpDirectory) {\n                Boolean hasAlpha = directory.getBooleanObject(WebpDirectory.TAG_HAS_ALPHA);\n                if (Boolean.TRUE.equals(hasAlpha)) {\n                    return ImageType.PNG;\n                }\n            }\n        }\n        return ImageType.JPG;\n    }\n\n    @Override\n    public Dimension getDimension(Metadata metadata) {\n        for (Directory directory : metadata.getDirectories()) {\n            if (directory instanceof WebpDirectory) {\n                Integer width = directory.getInteger(WebpDirectory.TAG_IMAGE_WIDTH);\n                Integer height = directory.getInteger(WebpDirectory.TAG_IMAGE_HEIGHT);\n                if (width != null && height != null) {\n                    return new Dimension(width, height);\n                }\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/ElementRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html;\n\nimport org.jsoup.nodes.Element;\n\n\n/**\n * HTML元素渲染器\n *\n * @author Draco\n * @since 2021-02-08\n */\npublic interface ElementRenderer {\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    boolean renderStart(Element element, HtmlRenderContext context);\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    default void renderEnd(Element element, HtmlRenderContext context) {\n    }\n\n    /**\n     * @return 支持的HTML标签\n     */\n    String[] supportedTags();\n\n    /**\n     * @return 是否为块状渲染，如果为true在Word中会另起一个Paragraph\n     */\n    boolean renderAsBlock();\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/ElementRendererProvider.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html;\n\n/**\n * HTML元素渲染器提供者\n *\n * @author Draco\n * @since 2022-10-21\n */\n@FunctionalInterface\npublic interface ElementRendererProvider {\n    /**\n     * 根据HTML元素名称获取渲染器\n     *\n     * @param tagNormalName HTML元素名称（小写）\n     * @return HTML元素渲染器\n     */\n    ElementRenderer get(String tagNormalName);\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/HtmlConstants.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html;\n\nimport org.apache.commons.compress.utils.Sets;\n\nimport java.util.Set;\n\n/**\n * HTML常量\n *\n * @author Draco\n * @since 2021-02-23\n */\npublic interface HtmlConstants {\n    String TAG_A = \"a\";\n    String TAG_IMG = \"img\";\n    String TAG_BR = \"br\";\n    String TAG_MATH = \"math\";\n    String TAG_HR = \"hr\";\n    String TAG_OL = \"ol\";\n    String TAG_UL = \"ul\";\n    String TAG_LI = \"li\";\n    String TAG_TABLE = \"table\";\n    String TAG_S = \"s\";\n    String TAG_DEL = \"del\";\n    /**\n     * HTML5不支持strike\n     */\n    String TAG_STRIKE = \"strike\";\n    String TAG_I = \"i\";\n    String TAG_EM = \"em\";\n    String TAG_B = \"b\";\n    String TAG_STRONG = \"strong\";\n    String TAG_U = \"u\";\n    String TAG_MARK = \"mark\";\n    String TAG_SUB = \"sub\";\n    String TAG_SUP = \"sup\";\n    String TAG_H1 = \"h1\";\n    String TAG_H2 = \"h2\";\n    String TAG_H3 = \"h3\";\n    String TAG_H4 = \"h4\";\n    String TAG_H5 = \"h5\";\n    String TAG_H6 = \"h6\";\n    /**\n     * HTML5不支持big\n     */\n    String TAG_BIG = \"big\";\n    String TAG_SMALL = \"small\";\n    String TAG_CAPTION = \"caption\";\n    String TAG_COLGROUP = \"colgroup\";\n    String TAG_COL = \"col\";\n    String TAG_TR = \"tr\";\n    String TAG_TH = \"th\";\n    String TAG_TD = \"td\";\n    String TAG_THEAD = \"thead\";\n    String TAG_TBODY = \"tbody\";\n    String TAG_TFOOT = \"tfoot\";\n\n    String TAG_FRAME = \"frame\";\n    String TAG_FRAMESET = \"frameset\";\n    String TAG_IFRAME = \"iframe\";\n    String TAG_NOFRAMES = \"noframes\";\n    String TAG_HTML = \"html\";\n    String TAG_HEAD = \"head\";\n    String TAG_BODY = \"body\";\n    String TAG_SCRIPT = \"script\";\n    String TAG_NOSCRIPT = \"noscript\";\n    String TAG_TEMPLATE = \"template\";\n\n    String TAG_SVG = \"svg\";\n    String TAG_RUBY = \"ruby\";\n    String TAG_RP = \"rp\";\n    String TAG_RT = \"rt\";\n    String TAG_FIGURE = \"figure\";\n    String TAG_FIGURE_CAPTION = \"figcaption\";\n    String TAG_PRE = \"pre\";\n    String TAG_XMP = \"xmp\";\n    String TAG_LATEX = \"latex\";\n\n    String ATTR_STYLE = \"style\";\n    String ATTR_SRC = \"src\";\n    String ATTR_WIDTH = \"width\";\n    String ATTR_HEIGHT = \"height\";\n    String ATTR_SPAN = \"span\";\n    String ATTR_ROWSPAN = \"rowspan\";\n    String ATTR_COLSPAN = \"colspan\";\n    String ATTR_HREF = \"href\";\n    String ATTR_TYPE = \"type\";\n    String ATTR_FRAME = \"frame\";\n    String ATTR_RULES = \"rules\";\n    /**\n     * 自定义属性：行索引\n     */\n    String ATTR_ROW_INDEX = \"_r\";\n    /**\n     * 自定义属性：列索引\n     */\n    String ATTR_COLUMN_INDEX = \"_c\";\n\n    String CSS_BACKGROUND = \"background\";\n    String CSS_BACKGROUND_COLOR = \"background-color\";\n    String CSS_BORDER = \"border\";\n    String CSS_BORDER_STYLE = \"border-style\";\n    String CSS_BORDER_WIDTH = \"border-width\";\n    String CSS_BORDER_COLOR = \"border-color\";\n    String CSS_FONT = \"font\";\n    String CSS_MARGIN = \"margin\";\n    String CSS_MARGIN_TOP = \"margin-top\";\n    String CSS_MARGIN_RIGHT = \"margin-right\";\n    String CSS_MARGIN_BOTTOM = \"margin-bottom\";\n    String CSS_MARGIN_LEFT = \"margin-left\";\n    String CSS_PADDING = \"padding\";\n    String CSS_PADDING_TOP = \"padding-top\";\n    String CSS_PADDING_RIGHT = \"padding-right\";\n    String CSS_PADDING_BOTTOM = \"padding-bottom\";\n    String CSS_PADDING_LEFT = \"padding-left\";\n    String CSS_FONT_STYLE = \"font-style\";\n    String CSS_FONT_VARIANT_CAPS = \"font-variant-caps\";\n    String CSS_FONT_WEIGHT = \"font-weight\";\n    String CSS_FONT_SIZE = \"font-size\";\n    String CSS_LINE_HEIGHT = \"line-height\";\n    String CSS_FONT_FAMILY = \"font-family\";\n    String CSS_TEXT_DECORATION = \"text-decoration\";\n    String CSS_TEXT_DECORATION_LINE = \"text-decoration-line\";\n    String CSS_TEXT_DECORATION_STYLE = \"text-decoration-style\";\n    String CSS_TEXT_DECORATION_COLOR = \"text-decoration-color\";\n    String CSS_TEXT_INDENT = \"text-indent\";\n    String CSS_VERTICAL_ALIGN = \"vertical-align\";\n    String CSS_VISIBILITY = \"visibility\";\n    String CSS_DISPLAY = \"display\";\n    String CSS_COLOR = \"color\";\n    String CSS_WIDTH = ATTR_WIDTH;\n    String CSS_MAX_WIDTH = \"max-width\";\n    String CSS_HEIGHT = ATTR_HEIGHT;\n    String CSS_MAX_HEIGHT = \"max-height\";\n    String CSS_BORDER_TOP = \"border-top\";\n    String CSS_BORDER_RIGHT = \"border-right\";\n    String CSS_BORDER_BOTTOM = \"border-bottom\";\n    String CSS_BORDER_LEFT = \"border-left\";\n    String CSS_BORDER_TOP_STYLE = \"border-top-style\";\n    String CSS_BORDER_RIGHT_STYLE = \"border-right-style\";\n    String CSS_BORDER_BOTTOM_STYLE = \"border-bottom-style\";\n    String CSS_BORDER_LEFT_STYLE = \"border-left-style\";\n    String CSS_BORDER_TOP_WIDTH = \"border-top-width\";\n    String CSS_BORDER_RIGHT_WIDTH = \"border-right-width\";\n    String CSS_BORDER_BOTTOM_WIDTH = \"border-bottom-width\";\n    String CSS_BORDER_LEFT_WIDTH = \"border-left-width\";\n    String CSS_BORDER_TOP_COLOR = \"border-top-color\";\n    String CSS_BORDER_RIGHT_COLOR = \"border-right-color\";\n    String CSS_BORDER_BOTTOM_COLOR = \"border-bottom-color\";\n    String CSS_BORDER_LEFT_COLOR = \"border-left-color\";\n    String CSS_FLOAT = \"float\";\n    String CSS_WHITE_SPACE = \"white-space\";\n    String CSS_LIST_STYLE = \"list-style\";\n    String CSS_LIST_STYLE_TYPE = \"list-style-type\";\n    String CSS_LIST_STYLE_POSITION = \"list-style-position\";\n    String CSS_BORDER_COLLAPSE = \"border-collapse\";\n    String CSS_BORDER_SPACING = \"border-spacing\";\n    String CSS_CAPTION_SIDE = \"caption-side\";\n    String CSS_LETTER_SPACING = \"letter-spacing\";\n    String CSS_TEXT_ALIGN = \"text-align\";\n\n    String NORMAL = \"normal\";\n    String ITALIC = \"italic\";\n    String OBLIQUE = \"oblique\";\n    String SMALL_CAPS = \"small-caps\";\n    String BOLD = \"bold\";\n    String BOLDER = \"bolder\";\n    String LIGHTER = \"lighter\";\n    String START = \"start\";\n    String LEFT = \"left\";\n    String END = \"end\";\n    String RIGHT = \"right\";\n    String CENTER = \"center\";\n    String JUSTIFY = \"justify\";\n    String JUSTIFY_ALL = \"justify-all\";\n    String TOP = \"top\";\n    String BOTTOM = \"bottom\";\n    String MIDDLE = \"middle\";\n    String AUTO = \"auto\";\n\n    String XX_SMALL = \"xx-small\";\n    String X_SMALL = \"x-small\";\n    String SMALL = \"small\";\n    String MEDIUM = \"medium\";\n    String LARGE = \"large\";\n    String X_LARGE = \"x-large\";\n    String XX_LARGE = \"xx-large\";\n    String XXX_LARGE = \"xxx-large\";\n\n    String SMALLER = \"smaller\";\n    String LARGER = \"larger\";\n\n    String THIN = \"thin\";\n    String THICK = \"thick\";\n\n    String PT = \"pt\";\n    String PC = \"pc\";\n    String IN = \"in\";\n    String CM = \"cm\";\n    String MM = \"mm\";\n    String PX = \"px\";\n    String EM = \"em\";\n    String REM = \"rem\";\n    String VW = \"vw\";\n    String VH = \"vh\";\n    String VMIN = \"vmin\";\n    String VMAX = \"vmax\";\n    String PERCENT = \"%\";\n    // 自定义单位\n    String EMU = \"emu\";\n    /**\n     * dxa的单位，twentieth of a point = 1 / 20 pt\n     */\n    String TWIP = \"twip\";\n\n    String SLASH = \"/\";\n    String COMMA = \",\";\n    String COLON = \":\";\n    String SHARP = \"#\";\n    String SEMICOLON = \";\";\n    String QUESTION = \"?\";\n    String PLUS = \"+\";\n    String MINUS = \"-\";\n    String LEFT_PARENTHESIS = \"(\";\n\n    String LINE_THROUGH = \"line-through\";\n    String UNDERLINE = \"underline\";\n    String SOLID = \"solid\";\n    String DOUBLE = \"double\";\n    String DOTTED = \"dotted\";\n    String DASHED = \"dashed\";\n    String WAVY = \"wavy\";\n    String NONE = \"none\";\n\n    String GROOVE = \"groove\";\n    String RIDGE = \"ridge\";\n    // 类似groove\n    String INSET = \"inset\";\n    // 类似ridge\n    String OUTSET = \"outset\";\n\n    String HIDDEN = \"hidden\";\n    String COLLAPSE = \"collapse\";\n\n    String SUPER = \"super\";\n    String SUB = \"sub\";\n\n    String NO_WRAP = \"nowrap\";\n    String PRE = \"pre\";\n    String PRE_WRAP = \"pre-wrap\";\n    String PRE_LINE = \"pre-line\";\n    String BREAK_SPACES = \"break-spaces\";\n\n    String INSIDE = \"inside\";\n    String OUTSIDE = \"outside\";\n\n    String VOID = \"void\";\n    String ABOVE = \"above\";\n    String BELOW = \"below\";\n    String H_SIDES = \"hsides\";\n    String V_SIDES = \"vsides\";\n    String LHS = \"lhs\";\n    String RHS = \"rhs\";\n    String BOX = \"box\";\n    String BORDER = \"border\";\n\n    String GROUPS = \"groups\";\n    String ROWS = \"rows\";\n    String COLS = \"cols\";\n    String ALL = \"all\";\n\n    Set<String> FONT_STYLES = Sets.newHashSet(NORMAL, ITALIC, OBLIQUE);\n    Set<String> FONT_VARIANTS = Sets.newHashSet(NORMAL, SMALL_CAPS);\n    Set<String> FONT_WEIGHTS = Sets.newHashSet(NORMAL, BOLD, BOLDER, LIGHTER);\n    Set<String> BORDER_STYLES = Sets.newHashSet(NONE, HIDDEN, DOTTED, DASHED, SOLID, DOUBLE, GROOVE, RIDGE, INSET, OUTSET);\n    // 不支持overline\n    Set<String> TEXT_DECORATION_LINES = Sets.newHashSet(UNDERLINE, LINE_THROUGH);\n    Set<String> TEXT_DECORATION_STYLES = Sets.newHashSet(SOLID, DOUBLE, DOTTED, DASHED, WAVY);\n    Set<String> LIST_STYLE_POSITIONS = Sets.newHashSet(INSIDE, OUTSIDE);\n\n    /**\n     * 可继承的样式\n     * <a href=\"https://www.w3.org/TR/CSS22/propidx.html\">Specification</a>\n     */\n    Set<String> INHERITABLE_STYLES = Sets.newHashSet(\n            \"azimuth\",\n            CSS_BORDER_COLLAPSE,\n            CSS_BORDER_SPACING,\n            CSS_CAPTION_SIDE,\n            CSS_COLOR,\n            \"cursor\",\n            \"direction\",\n            \"elevation\",\n            \"empty-cells\",\n            CSS_FONT_FAMILY,\n            CSS_FONT_SIZE,\n            CSS_FONT_STYLE,\n            CSS_FONT_VARIANT_CAPS,\n            CSS_FONT_WEIGHT,\n            CSS_FONT,\n            CSS_LETTER_SPACING,\n            CSS_LINE_HEIGHT,\n            \"list-style-image\",\n            \"list-style-position\",\n            CSS_LIST_STYLE_TYPE,\n            CSS_LIST_STYLE,\n            \"orphans\",\n            \"pitch-range\",\n            \"pitch\",\n            \"quotes\",\n            \"richness\",\n            \"speak-header\",\n            \"speak-numeral\",\n            \"speak-punctuation\",\n            \"speak\",\n            \"speech-rate\",\n            \"stress\",\n            CSS_TEXT_ALIGN,\n            CSS_TEXT_INDENT,\n            \"text-transform\",\n            CSS_VISIBILITY,\n            \"voice-family\",\n            \"volume\",\n            CSS_WHITE_SPACE,\n            \"widows\",\n            \"word-spacing\"\n    );\n\n    /**\n     * 需要保留的空标签\n     */\n    Set<String> KEEP_EMPTY_TAGS = Sets.newHashSet(TAG_LI, TAG_HR);\n\n    /**\n     * Word中一些主要的默认字体\n     */\n    Set<String> MAJOR_FONT = Sets.newHashSet(\"宋体\", \"SIMSUN\", \"新細明體\", \"TIMES NEW ROMAN\", \"ARIAL\");\n\n    String DEFINED_ITALIC = inlineStyle(CSS_FONT_STYLE, ITALIC);\n    String DEFINED_STRIKE = inlineStyle(CSS_TEXT_DECORATION_LINE, LINE_THROUGH);\n    String DEFINED_BOLD = inlineStyle(CSS_FONT_WEIGHT, BOLD);\n    String DEFINED_UNDERLINE = inlineStyle(CSS_TEXT_DECORATION_LINE, UNDERLINE);\n    String DEFINED_SUPERSCRIPT = inlineStyle(CSS_VERTICAL_ALIGN, SUPER);\n    String DEFINED_SUBSCRIPT = inlineStyle(CSS_VERTICAL_ALIGN, SUB);\n    String DEFINED_LARGER = inlineStyle(CSS_FONT_SIZE, LARGER);\n    String DEFINED_SMALLER = inlineStyle(CSS_FONT_SIZE, SMALLER);\n    String DEFINED_PRE = inlineStyle(CSS_WHITE_SPACE, PRE);\n\n    /**\n     * 生成行内样式声明\n     *\n     * @param key 样式属性\n     * @param value 样式值\n     * @return 行内样式声明\n     */\n    static String inlineStyle(String key, String value) {\n        return key + COLON + value + SEMICOLON;\n    }\n\n    /**\n     * @param fontName 字体名称\n     * @return 是否为主要字体\n     */\n    static boolean isMajorFont(String fontName) {\n        return MAJOR_FONT.contains(fontName.toUpperCase());\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/HtmlRenderConfig.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html;\n\nimport org.ddr.poi.html.util.CSSLength;\nimport org.ddr.poi.math.MathRenderConfig;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STLevelSuffix;\n\nimport java.util.List;\n\n/**\n * @author Draco\n * @since 2021-10-26\n */\npublic class HtmlRenderConfig {\n    private String globalFont;\n\n    private CSSLength globalFontSize;\n    private int globalFontSizeInHalfPoints;\n\n    private boolean showDefaultTableBorderInTableCell;\n    private List<ElementRenderer> customRenderers;\n\n    private int numberingIndent = -1;\n    private int numberingHanging = -1;\n    private STLevelSuffix.Enum numberingSpacing;\n\n    private final MathRenderConfig mathRenderConfig = new MathRenderConfig();\n\n    /**\n     * @return global font family\n     */\n    public String getGlobalFont() {\n        return globalFont;\n    }\n\n    public void setGlobalFont(String globalFont) {\n        this.globalFont = globalFont;\n    }\n\n    /**\n     * @return global font size\n     */\n    public CSSLength getGlobalFontSize() {\n        return globalFontSize;\n    }\n\n    public void setGlobalFontSize(CSSLength globalFontSize) {\n        this.globalFontSize = globalFontSize;\n        globalFontSizeInHalfPoints = globalFontSize == null ? 0 : globalFontSize.toHalfPoints();\n    }\n\n    public int getGlobalFontSizeInHalfPoints() {\n        return globalFontSizeInHalfPoints;\n    }\n\n    /**\n     * @return whether to show default table borders if the table inside a table cell\n     */\n    public boolean isShowDefaultTableBorderInTableCell() {\n        return showDefaultTableBorderInTableCell;\n    }\n\n    public void setShowDefaultTableBorderInTableCell(boolean showDefaultTableBorderInTableCell) {\n        this.showDefaultTableBorderInTableCell = showDefaultTableBorderInTableCell;\n    }\n\n    /**\n     * @return custom html tag renderers\n     */\n    public List<ElementRenderer> getCustomRenderers() {\n        return customRenderers;\n    }\n\n    public void setCustomRenderers(List<ElementRenderer> customRenderers) {\n        this.customRenderers = customRenderers;\n    }\n\n    /**\n     * @return custom numbering indent\n     */\n    public int getNumberingIndent() {\n        return numberingIndent;\n    }\n\n    public void setNumberingIndent(int numberingIndent) {\n        this.numberingIndent = numberingIndent;\n    }\n\n    /**\n     * @return custom numbering hanging\n     */\n    public int getNumberingHanging() {\n        return numberingHanging;\n    }\n\n    public void setNumberingHanging(int numberingHanging) {\n        this.numberingHanging = numberingHanging;\n    }\n\n    /**\n     * @return custom numbering spacing\n     */\n    public STLevelSuffix.Enum getNumberingSpacing() {\n        return numberingSpacing;\n    }\n\n    public void setNumberingSpacing(STLevelSuffix.Enum numberingSpacing) {\n        this.numberingSpacing = numberingSpacing;\n    }\n\n    public MathRenderConfig getMathRenderConfig() {\n        return mathRenderConfig;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/HtmlRenderContext.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html;\n\nimport com.deepoove.poi.render.RenderContext;\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.math.NumberUtils;\nimport org.apache.poi.openxml4j.exceptions.InvalidFormatException;\nimport org.apache.poi.util.Units;\nimport org.apache.poi.xwpf.usermodel.BodyType;\nimport org.apache.poi.xwpf.usermodel.IBody;\nimport org.apache.poi.xwpf.usermodel.IRunBody;\nimport org.apache.poi.xwpf.usermodel.SVGPictureData;\nimport org.apache.poi.xwpf.usermodel.SVGRelation;\nimport org.apache.poi.xwpf.usermodel.XWPFDocument;\nimport org.apache.poi.xwpf.usermodel.XWPFFooter;\nimport org.apache.poi.xwpf.usermodel.XWPFFootnote;\nimport org.apache.poi.xwpf.usermodel.XWPFHeader;\nimport org.apache.poi.xwpf.usermodel.XWPFHyperlinkRun;\nimport org.apache.poi.xwpf.usermodel.XWPFParagraph;\nimport org.apache.poi.xwpf.usermodel.XWPFPicture;\nimport org.apache.poi.xwpf.usermodel.XWPFRelation;\nimport org.apache.poi.xwpf.usermodel.XWPFRun;\nimport org.apache.poi.xwpf.usermodel.XWPFStyle;\nimport org.apache.poi.xwpf.usermodel.XWPFStyles;\nimport org.apache.poi.xwpf.usermodel.XWPFTable;\nimport org.apache.poi.xwpf.usermodel.XWPFTableCell;\nimport org.apache.xmlbeans.XmlCursor;\nimport org.apache.xmlbeans.XmlObject;\nimport org.apache.xmlbeans.impl.xb.xmlschema.SpaceAttribute;\nimport org.ddr.poi.html.util.CSSLength;\nimport org.ddr.poi.html.util.CSSLengthUnit;\nimport org.ddr.poi.html.util.CSSStyleUtils;\nimport org.ddr.poi.html.util.Colors;\nimport org.ddr.poi.html.util.InlineStyle;\nimport org.ddr.poi.html.util.NamedFontSize;\nimport org.ddr.poi.html.util.NumberingContext;\nimport org.ddr.poi.html.util.RenderUtils;\nimport org.ddr.poi.html.util.WhiteSpaceRule;\nimport org.ddr.poi.html.util.XWPFParagraphRuns;\nimport org.ddr.poi.math.MathMLUtils;\nimport org.ddr.poi.math.MathRenderConfig;\nimport org.ddr.poi.util.XmlUtils;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.TextNode;\nimport org.openxmlformats.schemas.drawingml.x2006.main.CTBlip;\nimport org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObjectFrameLocking;\nimport org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualGraphicFrameProperties;\nimport org.openxmlformats.schemas.drawingml.x2006.main.CTOfficeArtExtension;\nimport org.openxmlformats.schemas.drawingml.x2006.main.CTOfficeArtExtensionList;\nimport org.openxmlformats.schemas.drawingml.x2006.picture.CTPicture;\nimport org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTAnchor;\nimport org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;\nimport org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTPosH;\nimport org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTPosV;\nimport org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STAlignH;\nimport org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STAlignV;\nimport org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STRelFromH;\nimport org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STRelFromV;\nimport org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STWrapText;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTColor;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDrawing;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFonts;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHyperlink;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTMarkupRange;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageMar;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageSz;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRPr;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTShd;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTStyle;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblBorders;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTUnderline;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STOnOff;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STShd;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STStyleType;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STThemeColor;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STUnderline;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalAlignRun;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.xml.namespace.QName;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.math.BigInteger;\nimport java.net.URI;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * HTML字符串渲染上下文\n *\n * @author Draco\n * @since 2021-02-08\n */\npublic class HtmlRenderContext extends RenderContext<String> {\n    private static final Logger log = LoggerFactory.getLogger(HtmlRenderContext.class);\n\n\n    /**\n     * 默认字号 小四 12pt 16px\n     */\n    private static final CSSLength DEFAULT_FONT_SIZE = new CSSLength(12, CSSLengthUnit.PT);\n    /**\n     * 默认超链接颜色\n     */\n    private static final String DEFAULT_HYPERLINK_COLOR = \"0563C1\";\n\n    /**\n     * HTML元素渲染器提供者\n     */\n    private final ElementRendererProvider rendererProvider;\n\n    /**\n     * 父容器（子元素通常为段落/表格）栈，主要用于渲染HTML表格时父容器的切换\n     */\n    private final LinkedList<IBody> ancestors = new LinkedList<>();\n\n    /**\n     * 行内样式栈，最近声明的样式最先生效\n     */\n    private final LinkedList<InlineStyle> inlineStyles = new LinkedList<>();\n\n    /**\n     * 字号栈，一些相对大小的字号值将被进行换算\n     */\n    private final LinkedList<Integer> fontSizesInHalfPoints = new LinkedList<>();\n\n    /**\n     * 列表上下文，用于处理嵌套列表\n     */\n    private final NumberingContext numberingContext;\n    /**\n     * 默认字号\n     */\n    private final CSSLength defaultFontSize;\n    /**\n     * 页面宽度\n     */\n    private final CSSLength pageWidth;\n    /**\n     * 页面高度\n     */\n    private final CSSLength pageHeight;\n    /**\n     * 页面顶部边距\n     */\n    private final CSSLength marginTop;\n    /**\n     * 页面右侧边距\n     */\n    private final CSSLength marginRight;\n    /**\n     * 页面底部边距\n     */\n    private final CSSLength marginBottom;\n    /**\n     * 页面左侧边距\n     */\n    private final CSSLength marginLeft;\n    /**\n     * 可用页面宽度\n     */\n    private final int availablePageWidth;\n    /**\n     * 可用页面高度\n     */\n    private final int availablePageHeight;\n    /**\n     * 占位符所在段落的样式ID\n     */\n    private String placeholderStyleId;\n\n    /**\n     * 当前Run元素，可能为超链接\n     */\n    private XWPFRun currentRun;\n\n    /**\n     * 全局字体，声明后所有样式中的字体将失效\n     */\n    private String globalFont;\n    /**\n     * 全局字号，声明后所有样式中的字号将失效\n     */\n    private BigInteger globalFontSize;\n    /**\n     * 嵌套表格是否默认显示边框\n     */\n    private boolean showDefaultTableBorderInTableCell;\n    /**\n     * 数学公式渲染配置\n     */\n    private MathRenderConfig mathRenderConfig;\n\n    /**\n     * 块状元素深度计数器\n     */\n    private int blockLevel;\n\n    /**\n     * 全局xml指针，保证其总是处于将要插入内容的位置，仅在需要移动之前进行push，适时pop还原位置\n     */\n    private final XmlCursor globalCursor;\n\n    /**\n     * 防重段落\n     */\n    private XWPFParagraph dedupeParagraph;\n\n    /**\n     * 同一段落内的前一个文本节点\n     */\n    private TextWrapper previousText;\n\n    /**\n     * 前一个图片所在节点\n     */\n    private CTR previousDrawingRun;\n\n    /**\n     * 构造方法\n     *\n     * @param context 原始渲染上下文\n     */\n    public HtmlRenderContext(RenderContext<String> context, ElementRendererProvider rendererProvider) {\n        super(context.getEleTemplate(), context.getData(), context.getTemplate());\n        this.rendererProvider = rendererProvider;\n        globalCursor = getRun().getCTR().newCursor();\n\n        numberingContext = new NumberingContext(getXWPFDocument());\n\n        int w = RenderUtils.A4_WIDTH;\n        int h = RenderUtils.A4_HEIGHT;\n        int top = RenderUtils.DEFAULT_TOP_MARGIN;\n        int right = RenderUtils.DEFAULT_RIGHT_MARGIN;\n        int bottom = RenderUtils.DEFAULT_BOTTOM_MARGIN;\n        int left = RenderUtils.DEFAULT_LEFT_MARGIN;\n        CTSectPr sectPr = getXWPFDocument().getDocument().getBody().getSectPr();\n        if (sectPr != null) {\n            CTPageSz pgSz = sectPr.getPgSz();\n            // 页面尺寸单位是twip\n            if (pgSz != null) {\n                w = pgSz.getW().intValue();\n                h = pgSz.getH().intValue();\n            }\n\n            CTPageMar pgMar = sectPr.getPgMar();\n            if (pgMar != null) {\n                top = pgMar.getTop().intValue();\n                right = pgMar.getRight().intValue();\n                bottom = pgMar.getBottom().intValue();\n                left = pgMar.getLeft().intValue();\n            }\n        }\n\n        pageWidth = new CSSLength(w, CSSLengthUnit.TWIP);\n        pageHeight = new CSSLength(h, CSSLengthUnit.TWIP);\n        marginTop = new CSSLength(top, CSSLengthUnit.TWIP);\n        marginRight = new CSSLength(right, CSSLengthUnit.TWIP);\n        marginBottom = new CSSLength(bottom, CSSLengthUnit.TWIP);\n        marginLeft = new CSSLength(left, CSSLengthUnit.TWIP);\n        availablePageWidth = new CSSLength(w - left - right, CSSLengthUnit.TWIP).toEMU();\n        availablePageHeight = new CSSLength(h - top - bottom, CSSLengthUnit.TWIP).toEMU();\n\n        int fontSize = getXWPFDocument().getStyles().getDefaultRunStyle().getFontSize();\n        defaultFontSize = fontSize > 0 ? new CSSLength(fontSize, CSSLengthUnit.PT) : DEFAULT_FONT_SIZE;\n\n        extractPlaceholderStyle();\n    }\n\n    /**\n     * 抽取占位符所在段落的样式\n     */\n    private void extractPlaceholderStyle() {\n        XWPFRun run = getRun();\n        IRunBody runParent = run.getParent();\n        if (runParent instanceof XWPFParagraph) {\n            XWPFParagraph paragraph = (XWPFParagraph) runParent;\n            String styleId = paragraph.getStyleID();\n            boolean existsRPr = run.getCTR().isSetRPr();\n\n            if (styleId == null && !existsRPr) {\n                return;\n            } else if (styleId != null && !existsRPr) {\n                placeholderStyleId = styleId;\n                return;\n            }\n\n            XWPFStyles styles = getXWPFDocument().getStyles();\n            CTStyle newCTStyle = CTStyle.Factory.newInstance();\n            newCTStyle.setCustomStyle(STOnOff.TRUE);\n            newCTStyle.setType(STStyleType.PARAGRAPH);\n            newCTStyle.addNewHidden();\n            newCTStyle.setRPr(run.getCTR().getRPr());\n            XmlUtils.removeNamespaces(newCTStyle.getRPr());\n\n            String newStyleId = styleId + styles.getNumberOfStyles();\n            newCTStyle.setStyleId(newStyleId);\n            newCTStyle.addNewName().setVal(newStyleId);\n            placeholderStyleId = newStyleId;\n\n            if (styleId != null) {\n                newCTStyle.addNewBasedOn().setVal(styleId);\n            }\n\n            XWPFStyle newStyle = new XWPFStyle(newCTStyle, styles);\n            styles.addStyle(newStyle);\n\n            paragraph.setStyle(newStyleId);\n        }\n    }\n\n    @Override\n    public IBody getContainer() {\n        IBody container = ancestors.peek();\n        return container == null ? super.getContainer() : container;\n    }\n\n    /**\n     * 父容器入栈\n     *\n     * @param body 父容器\n     */\n    public void pushContainer(IBody body) {\n        ancestors.push(body);\n    }\n\n    /**\n     * 父容器出栈\n     */\n    public void popContainer() {\n        ancestors.pop();\n    }\n\n    /**\n     * 获取最近的段落，如果当前最近位置的内容元素是表格，则创建一个与之平级的段落\n     *\n     * @return 最近的段落\n     */\n    public XWPFParagraph getClosestParagraph() {\n        if (globalCursor.getObject() == getRun().getCTR()) {\n            return (XWPFParagraph) getRun().getParent();\n        }\n\n        globalCursor.push();\n        XWPFParagraph paragraph = null;\n        if (globalCursor.toPrevSibling()) {\n            XmlObject object = globalCursor.getObject();\n            if (object instanceof CTP) {\n                paragraph = getContainer().getParagraph((CTP) object);\n            } else {\n                // pop() is safer than toNextSibling()\n                globalCursor.pop();\n                globalCursor.push();\n                paragraph = newParagraph(null, globalCursor);\n                RenderUtils.paragraphStyle(this, paragraph, CSSStyleUtils.EMPTY_STYLE);\n            }\n        }\n        globalCursor.pop();\n\n        if (paragraph != null) {\n            return paragraph;\n        }\n\n        throw new IllegalStateException(\"No paragraph in stack\");\n    }\n\n    /**\n     * 开始渲染超链接\n     *\n     * @param uri 链接地址\n     */\n    public void startHyperlink(String uri) {\n        try {\n            URI.create(uri);\n        } catch (Exception e) {\n            log.warn(\"Illegal href\", e);\n            uri = \"#\";\n        }\n        if (isBlocked()) {\n            XWPFParagraph paragraph = getClosestParagraph();\n            currentRun = paragraph.createHyperlinkRun(uri);\n            if (dedupeParagraph == paragraph) {\n                unmarkDedupe();\n            }\n        } else {\n            // 在占位符之前插入超链接\n            String rId = getRun().getParent().getPart().getPackagePart()\n                    .addExternalRelationship(uri, XWPFRelation.HYPERLINK.getRelation()).getId();\n            XmlCursor xmlCursor = getRun().getCTR().newCursor();\n            xmlCursor.insertElement(XmlUtils.HYPERLINK_QNAME);\n            xmlCursor.toPrevSibling();\n            CTHyperlink ctHyperlink = (CTHyperlink) xmlCursor.getObject();\n            xmlCursor.dispose();\n            ctHyperlink.setId(rId);\n            ctHyperlink.addNewR();\n            currentRun = new XWPFHyperlinkRun(ctHyperlink, ctHyperlink.getRArray(0), getRun().getParent());\n        }\n    }\n\n    /**\n     * 结束渲染超链接\n     */\n    public void endHyperlink() {\n        currentRun = null;\n    }\n\n    /**\n     * 新建段落\n     *\n     * @param container 容器\n     * @param cursor xml指针\n     * @return 段落\n     */\n    public XWPFParagraph newParagraph(IBody container, XmlCursor cursor) {\n        if (container == null) {\n            container = getContainer();\n        }\n        XWPFParagraph xwpfParagraph = container.insertNewParagraph(cursor);\n        if (placeholderStyleId != null) {\n            xwpfParagraph.setStyle(placeholderStyleId);\n        }\n        markDedupe(xwpfParagraph);\n        previousText = null;\n        adjustPicture();\n        return xwpfParagraph;\n    }\n\n    private void adjustPicture() {\n        if (previousDrawingRun != null) {\n            XmlCursor xmlCursor = previousDrawingRun.newCursor();\n            List<CTDrawing> drawings = new ArrayList<>(previousDrawingRun.getDrawingList());\n            boolean hasText = false;\n            while (xmlCursor.toPrevSibling()) {\n                if (XmlUtils.R_QNAME.equals(xmlCursor.getName())) {\n                    CTR ctr = (CTR) xmlCursor.getObject();\n                    for (int i = 0, l = ctr.sizeOfTArray(); i < l; i++) {\n                        CTText ctText = ctr.getTArray(i);\n                        if (StringUtils.isNotBlank(ctText.getStringValue())) {\n                            hasText = true;\n                            break;\n                        }\n                    }\n                    for (int i = 0, l = ctr.sizeOfDrawingArray(); i < l; i++) {\n                        drawings.add(ctr.getDrawingArray(i));\n                    }\n                } else if (MathMLUtils.OMATH_QNAME.equals(xmlCursor.getName())) {\n                    hasText = true;\n                }\n            }\n            if (!hasText) {\n                for (CTDrawing drawing : drawings) {\n                    CTAnchor ctAnchor = RenderUtils.inlineToAnchor(drawing);\n                    ctAnchor.addNewWrapTopAndBottom();\n\n                    CTPosH ctPosH = ctAnchor.addNewPositionH();\n                    ctPosH.setRelativeFrom(STRelFromH.MARGIN);\n                    ctPosH.setAlign(STAlignH.LEFT);\n\n                    CTPosV ctPosV = ctAnchor.addNewPositionV();\n                    ctPosV.setRelativeFrom(STRelFromV.PARAGRAPH);\n                    ctPosV.setAlign(STAlignV.TOP);\n                }\n            }\n            xmlCursor.dispose();\n            previousDrawingRun = null;\n        }\n    }\n\n    /**\n     * 新建CTR\n     *\n     * @return CTR\n     */\n    public CTR newRun() {\n        // 超链接虽然不是段落，但是内部可以容纳多个run\n        if (currentRun instanceof XWPFHyperlinkRun) {\n            XmlCursor xmlCursor = currentRun.getCTR().newCursor();\n            CTR ctr;\n            if (xmlCursor.toFirstChild()) {\n                ctr = ((XWPFHyperlinkRun) currentRun).getCTHyperlink().addNewR();\n            } else {\n                // run没有内容则直接复用\n                ctr = currentRun.getCTR();\n            }\n            xmlCursor.dispose();\n            // 默认链接样式\n            initHyperlinkStyle(ctr);\n\n            return ctr;\n        }\n        // 考虑到样式可能不一致，总是创建新的run\n        if (isBlocked()) {\n            XWPFParagraph paragraph = getClosestParagraph();\n            currentRun = paragraph.createRun();\n            if (dedupeParagraph == paragraph) {\n                unmarkDedupe();\n            }\n        } else {\n            // 在占位符之前插入run\n            XmlCursor xmlCursor = getRun().getCTR().newCursor();\n            xmlCursor.insertElement(XmlUtils.R_QNAME);\n            xmlCursor.toPrevSibling();\n            CTR ctr = (CTR) xmlCursor.getObject();\n            xmlCursor.dispose();\n            currentRun = new XWPFRun(ctr, getRun().getParent());\n        }\n        return currentRun.getCTR();\n    }\n\n    /**\n     * 初始化超链接样式\n     *\n     * @param ctr CTR\n     */\n    private void initHyperlinkStyle(CTR ctr) {\n        CTRPr rPr = RenderUtils.getRPr(ctr);\n        CTColor ctColor = rPr.addNewColor();\n        ctColor.setVal(DEFAULT_HYPERLINK_COLOR);\n        ctColor.setThemeColor(STThemeColor.HYPERLINK);\n\n        rPr.addNewU().setVal(STUnderline.SINGLE);\n    }\n\n    /**\n     * 获取最近的表格，仅可在渲染表格及其内部元素的时候使用\n     *\n     * @return 最近的表格\n     */\n    public XWPFTable getClosestTable() {\n        globalCursor.push();\n        XWPFTable table = null;\n        if (globalCursor.toPrevSibling()) {\n            XmlObject object = globalCursor.getObject();\n            if (object instanceof CTTbl) {\n                table = getContainer().getTable((CTTbl) object);\n            }\n        }\n        globalCursor.pop();\n\n        if (table != null) {\n            return table;\n        }\n\n        throw new IllegalStateException(\"No table in stack\");\n    }\n\n    /**\n     * 行内样式入栈\n     *\n     * @param inlineStyle 样式声明\n     * @param block 是否为块状元素\n     */\n    public void pushInlineStyle(CSSStyleDeclarationImpl inlineStyle, boolean block) {\n        String newFontSize = inlineStyle.getFontSize();\n        // 默认值表示未声明字号\n        int fontSize = Integer.MIN_VALUE;\n        if (StringUtils.isNotBlank(newFontSize)) {\n            NamedFontSize namedFontSize = NamedFontSize.of(newFontSize);\n            if (namedFontSize != null) {\n                // 固定名称的字号\n                fontSize = namedFontSize.getSize().toHalfPoints();\n            } else if (HtmlConstants.SMALLER.equalsIgnoreCase(newFontSize)) {\n                // 相对小一号\n                int inheritedFontSize = getInheritedFontSizeInHalfPoints();\n                fontSize = RenderUtils.smallerFontSizeInHalfPoints(inheritedFontSize);\n            } else if (HtmlConstants.LARGER.equalsIgnoreCase(newFontSize)) {\n                // 相对大一号\n                int inheritedFontSize = getInheritedFontSizeInHalfPoints();\n                fontSize = RenderUtils.largerFontSizeInHalfPoints(inheritedFontSize);\n            } else {\n                CSSLength cssLength = CSSLength.of(newFontSize);\n                if (cssLength.isValid()) {\n                    if (cssLength.getUnit() == CSSLengthUnit.PERCENT) {\n                        fontSize = (int) Math.rint(getInheritedFontSizeInHalfPoints()\n                                * cssLength.getValue() * cssLength.getUnit().absoluteFactor());\n                    } else {\n                        int emu = lengthToEMU(cssLength);\n                        fontSize = emu * 2 / Units.EMU_PER_POINT;\n                    }\n                }\n            }\n        }\n        fontSizesInHalfPoints.push(fontSize);\n\n        // text-decoration-line 在继承时需要合并\n        String textDecorationLine = inlineStyle.getPropertyValue(HtmlConstants.CSS_TEXT_DECORATION_LINE);\n        if (StringUtils.isNotBlank(textDecorationLine) && !HtmlConstants.NONE.equals(textDecorationLine)) {\n            Set<String> remainValues = new HashSet<>(HtmlConstants.TEXT_DECORATION_LINES);\n            String[] values = StringUtils.split(textDecorationLine, ' ');\n            for (String value : values) {\n                remainValues.remove(value);\n            }\n\n            if (!remainValues.isEmpty()) {\n                StringBuilder lines = new StringBuilder(textDecorationLine);\n                for (InlineStyle inheritedStyle : inlineStyles) {\n                    String s = inheritedStyle.getDeclaration().getPropertyValue(HtmlConstants.CSS_TEXT_DECORATION_LINE);\n                    if (HtmlConstants.NONE.equals(s)) {\n                        break;\n                    } else if (remainValues.contains(s)) {\n                        lines.append(' ').append(s);\n                        remainValues.remove(s);\n                        if (remainValues.isEmpty()) {\n                            break;\n                        }\n                    }\n                }\n                if (lines.length() > textDecorationLine.length()) {\n                    inlineStyle.setProperty(HtmlConstants.CSS_TEXT_DECORATION_LINE, lines.toString(), null);\n                }\n            }\n        }\n\n        inlineStyles.push(new InlineStyle(inlineStyle, block));\n    }\n\n    /**\n     * 行内样式出栈\n     */\n    public void popInlineStyle() {\n        fontSizesInHalfPoints.pop();\n        inlineStyles.pop();\n    }\n\n    /**\n     * 当前元素的样式声明，在HTML元素渲染开始时立即调用才可得到正确的声明，因为在解析的过程中可能会动态插入样式\n     *\n     * @return 当前元素的样式声明\n     */\n    public CSSStyleDeclarationImpl currentElementStyle() {\n        InlineStyle inlineStyle = inlineStyles.peek();\n        return inlineStyle == null ? CSSStyleUtils.EMPTY_STYLE : inlineStyle.getDeclaration();\n    }\n\n    /**\n     * 获取样式值，将被转换为小写\n     *\n     * @param property 样式名称\n     * @return 样式值，未声明时返回空字符串\n     */\n    public String getPropertyValue(String property) {\n        return getPropertyValue(property, false);\n    }\n\n    /**\n     * 获取样式值，将被转换为小写\n     *\n     * @param property 样式名称\n     * @param inlineOnly 是否仅获取行内元素样式\n     * @return 样式值，未声明时返回空字符串\n     */\n    public String getPropertyValue(String property, boolean inlineOnly) {\n        return getPropertyValue(property, false, inlineOnly);\n    }\n\n    /**\n     * 获取样式值\n     *\n     * @param property 样式名称\n     * @param caseSensitive 是否大小写无关，如果无关则将转换为小写，否则保留原始值\n     * @param inlineOnly 是否仅获取行内元素样式\n     * @return 样式值，未声明时返回空字符串\n     */\n    public String getPropertyValue(String property, boolean caseSensitive, boolean inlineOnly) {\n        for (InlineStyle inlineStyle : inlineStyles) {\n            if (inlineOnly && inlineStyle.isBlock()) {\n                break;\n            }\n            String propertyValue = inlineStyle.getDeclaration().getPropertyValue(property);\n            if (StringUtils.isNotBlank(propertyValue)) {\n                return caseSensitive ? propertyValue : propertyValue.toLowerCase();\n            }\n        }\n        return \"\";\n    }\n\n    /**\n     * @return Word中设置的默认字号\n     */\n    public CSSLength getDefaultFontSize() {\n        return defaultFontSize;\n    }\n\n    /**\n     * @return 获取当前元素继承的字号，以“半点”为单位\n     */\n    public int getInheritedFontSizeInHalfPoints() {\n        for (Integer fontSize : fontSizesInHalfPoints) {\n            if (fontSize > 0) {\n                return fontSize;\n            }\n        }\n        return defaultFontSize.toHalfPoints();\n    }\n\n    /**\n     * @return 父容器的可用宽度，以EMU为单位\n     */\n    public int getAvailableWidthInEMU() {\n        IBody container = getContainer();\n        if (container.getPartType() == BodyType.DOCUMENT) {\n            return availablePageWidth;\n        } else {\n            return RenderUtils.getAvailableWidthInEMU(container);\n        }\n    }\n\n    /**\n     * 考虑约束计算长度，以EMU为单位\n     *\n     * @param length 长度声明\n     * @param maxLength 最大长度声明\n     * @param naturalEMU 原始长度\n     * @param parentEMU 父容器长度\n     * @return 以EMU为单位的长度值\n     */\n    public int computeLengthInEMU(String length, String maxLength, int naturalEMU, int parentEMU) {\n        int emu = naturalEMU;\n\n        if (length.length() > 0) {\n            CSSLength cssLength = CSSLength.of(length);\n            if (cssLength.isValid()) {\n                emu = computeLengthInEMU(cssLength, naturalEMU, parentEMU);\n            }\n        }\n\n        if (maxLength.length() > 0) {\n            CSSLength cssLength = CSSLength.of(maxLength);\n            if (cssLength.isValid()) {\n                int maxEMU = computeLengthInEMU(cssLength, naturalEMU, parentEMU);\n                emu = Math.min(maxEMU, emu);\n            }\n        }\n\n        return Math.min(emu, parentEMU);\n    }\n\n    /**\n     * 考虑约束计算长度，以EMU为单位\n     *\n     * @param cssLength 长度声明\n     * @param naturalEMU 原始长度\n     * @param parentEMU 父容器长度\n     * @return 以EMU为单位的长度值\n     */\n    public int computeLengthInEMU(CSSLength cssLength, int naturalEMU, int parentEMU) {\n        int length;\n        if (cssLength.getUnit() == CSSLengthUnit.PERCENT) {\n            if (parentEMU != Integer.MAX_VALUE) {\n                length = (int) (parentEMU * cssLength.getValue() * cssLength.getUnit().absoluteFactor());\n            } else {\n                length = naturalEMU;\n            }\n        } else {\n            length = lengthToEMU(cssLength);\n        }\n        return length;\n    }\n\n    /**\n     * 渲染文本\n     *\n     * @param text 文本\n     */\n    public void renderText(String text) {\n        String whiteSpace = getPropertyValue(HtmlConstants.CSS_WHITE_SPACE);\n        WhiteSpaceRule rule = WhiteSpaceRule.of(whiteSpace, WhiteSpaceRule.NORMAL);\n\n        StringBuilder sb = StringUtil.borrowBuilder();\n        boolean mergeWhitespace = false;\n\n        // https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references\n        int len = text.length();\n        int c;\n\n        if (!rule.isKeepTrailingSpace()) {\n            boolean reachedLastNonWhite = false;\n            for (int i = len - 1; i >= 0; i -= Character.charCount(c)) {\n                c = text.codePointAt(i);\n                switch (c) {\n                    case ' ':\n                    case '\\r':\n                    case '\\n':\n                    case '\\t':\n                    case 173: // soft hyphen\n                    case 8203: // zero width space\n                    case 8204: // zero width non-joiner\n                    case 8205: // zero width joiner\n                    case 8206: // lrm\n                    case 8207: // rlm\n                    case 8288: // word joiner\n                    case 8289: // apply function\n                    case 8290: // invisible times\n                    case 8291: // invisible separator\n                        len = i;\n                        break;\n                    default:\n                        if (Character.getType(c) == 16) {\n                            len = i;\n                        } else {\n                            reachedLastNonWhite = true;\n                        }\n                        break;\n                }\n                if (reachedLastNonWhite) {\n                    break;\n                }\n            }\n        }\n        if (len == 0) {\n            return;\n        }\n        boolean endTrimmed = len < text.length();\n        CTR ctr = newRun();\n\n        for (int i = 0; i < len; i += Character.charCount(c)) {\n            c = text.codePointAt(i);\n            switch (c) {\n                case '\\r':\n                    if (i + 1 < len && text.codePointAt(i + 1) == '\\n') {\n                        continue;\n                    }\n                    if (rule.isKeepLineBreak()) {\n                        addText(ctr, sb, false);\n                        ctr.addNewCr();\n                    } else {\n                        mergeWhitespace = true;\n                    }\n                    break;\n                case '\\n':\n                    if (rule.isKeepLineBreak()) {\n                        addText(ctr, sb, false);\n                        ctr.addNewBr();\n                    } else {\n                        mergeWhitespace = true;\n                    }\n                    break;\n                case ' ':\n                    if (rule.isKeepSpaceAndTab()) {\n                        sb.appendCodePoint(c);\n                    } else {\n                        mergeWhitespace = true;\n                    }\n                    break;\n                case '\\t':\n                    if (rule.isKeepSpaceAndTab()) {\n                        addText(ctr, sb, false);\n                        ctr.addNewTab();\n                    } else {\n                        mergeWhitespace = true;\n                    }\n                    break;\n                case 160: // nbsp\n                case 8192: // enquad\n                case 8193: // emquad\n                case 8194: // ensp\n                case 8195: // emsp\n                case 8196: // emsp13\n                case 8197: // emsp14\n                case 8199: // numsp\n                case 8200: // puncsp\n                case 8201: // thinsp\n                case 8202: // hairsp\n                case 8239: // narrow space\n                case 8287: // medium space\n                    if (mergeWhitespace) {\n                        sb.append(' ');\n                        mergeWhitespace = false;\n                    }\n                    sb.append(' ');\n                    break;\n                case 173: // soft hyphen\n                case 8203: // zero width space\n                case 8204: // zero width non-joiner\n                case 8205: // zero width joiner\n                case 8206: // lrm\n                case 8207: // rlm\n                case 8288: // word joiner\n                case 8289: // apply function\n                case 8290: // invisible times\n                case 8291: // invisible separator\n                    continue;\n                default:\n                    if (Character.getType(c) == 16) {\n                        continue;\n                    }\n                    if (mergeWhitespace) {\n                        if (sb.length() > 0) {\n                            sb.append(' ');\n                        } else if (previousText != null && !previousText.isEndTrimmed()) {\n                            sb.append(' ');\n                            previousText = null;\n                        }\n                        mergeWhitespace = false;\n                    }\n                    sb.appendCodePoint(c);\n                    if (previousText != null && previousText.isEndTrimmed()) {\n                        CTText previous = previousText.getText();\n                        previous.setStringValue(previous.getStringValue() + ' ');\n                        previous.setSpace(SpaceAttribute.Space.PRESERVE);\n                        previousText = null;\n                    }\n                    break;\n            }\n        }\n\n        addText(ctr, sb, endTrimmed);\n        StringUtil.releaseBuilder(sb);\n\n        // 应用样式\n        applyTextStyle(ctr);\n\n        if (!(currentRun instanceof XWPFHyperlinkRun)) {\n            currentRun = null;\n        }\n    }\n\n    private void addText(CTR ctr, StringBuilder sb, boolean endTrimmed) {\n        if (sb.length() > 0) {\n            CTText ctText = ctr.addNewT();\n            String text = sb.toString();\n            ctText.setStringValue(text);\n            if (text.charAt(0) == ' ' || text.charAt(sb.length() - 1) == ' ') {\n                ctText.setSpace(SpaceAttribute.Space.PRESERVE);\n            }\n            sb.delete(0, sb.length());\n            previousText = new TextWrapper(ctText, endTrimmed);\n        }\n    }\n\n    /**\n     * 应用文本样式\n     *\n     * @param ctr CTR\n     */\n    private void applyTextStyle(CTR ctr) {\n        CTRPr rPr = RenderUtils.getRPr(ctr);\n\n        // 字体，如果声明了全局字体则忽略样式声明\n        String fontFamily = StringUtils.isBlank(globalFont) ? getPropertyValue(HtmlConstants.CSS_FONT_FAMILY) : globalFont;\n        if (StringUtils.isNotBlank(fontFamily)) {\n            CTFonts ctFonts = rPr.addNewRFonts();\n            // ASCII\n            ctFonts.setAscii(fontFamily);\n            // High ANSI\n            ctFonts.setHAnsi(fontFamily);\n            // Complex Script\n            ctFonts.setCs(fontFamily);\n            // East Asian\n            ctFonts.setEastAsia(fontFamily);\n        }\n\n        // 字号\n        if (globalFontSize == null) {\n            String fontSize = getPropertyValue(HtmlConstants.CSS_FONT_SIZE);\n            if (StringUtils.isNotBlank(fontSize)) {\n                int sz = getInheritedFontSizeInHalfPoints();\n                rPr.addNewSz().setVal(BigInteger.valueOf(sz));\n            }\n        } else {\n            // 如果定义了全局字号则忽略样式声明\n            rPr.addNewSz().setVal(globalFontSize);\n        }\n\n        // 加粗\n        String fontWeight = getPropertyValue(HtmlConstants.CSS_FONT_WEIGHT);\n        if (fontWeight.contains(HtmlConstants.BOLD)) {\n            rPr.addNewB();\n        } else if (NumberUtils.isParsable(fontWeight) && Float.parseFloat(fontWeight) > 500) {\n            rPr.addNewB();\n        }\n\n        // 斜体\n        String fontStyle = getPropertyValue(HtmlConstants.CSS_FONT_STYLE);\n        if (HtmlConstants.ITALIC.equals(fontStyle) || HtmlConstants.OBLIQUE.equals(fontStyle)) {\n            rPr.addNewI();\n        }\n\n        // 颜色\n        String color = getPropertyValue(HtmlConstants.CSS_COLOR);\n        if (StringUtils.isNotBlank(color)) {\n            String hex = Colors.fromStyle(color);\n            RenderUtils.getColor(rPr).setVal(hex);\n        }\n\n        String caps = getPropertyValue(HtmlConstants.CSS_FONT_VARIANT_CAPS);\n        if (HtmlConstants.SMALL_CAPS.equals(caps)) {\n            rPr.addNewSmallCaps();\n        }\n\n        // 中划线/下划线\n        String textDecoration = getPropertyValue(HtmlConstants.CSS_TEXT_DECORATION_LINE);\n        if (HtmlConstants.NONE.equals(textDecoration)) {\n            RenderUtils.getUnderline(rPr).setVal(STUnderline.NONE);\n        } else {\n            if (StringUtils.contains(textDecoration, HtmlConstants.LINE_THROUGH)) {\n                rPr.addNewStrike();\n            }\n            if (StringUtils.contains(textDecoration, HtmlConstants.UNDERLINE)) {\n                CTUnderline ctUnderline = RenderUtils.getUnderline(rPr);\n                String textDecorationStyle = getPropertyValue(HtmlConstants.CSS_TEXT_DECORATION_STYLE);\n                ctUnderline.setVal(RenderUtils.underline(textDecorationStyle));\n                String textDecorationColor = getPropertyValue(HtmlConstants.CSS_TEXT_DECORATION_COLOR);\n                if (StringUtils.isNotBlank(textDecorationColor)) {\n                    String hex = Colors.fromStyle(textDecorationColor);\n                    ctUnderline.setColor(hex);\n                }\n            }\n        }\n\n        // 上下标\n        String verticalAlign = getPropertyValue(HtmlConstants.CSS_VERTICAL_ALIGN);\n        if (HtmlConstants.SUPER.equals(verticalAlign)) {\n            rPr.addNewVertAlign().setVal(STVerticalAlignRun.SUPERSCRIPT);\n        } else if (HtmlConstants.SUB.equals(verticalAlign)) {\n            rPr.addNewVertAlign().setVal(STVerticalAlignRun.SUBSCRIPT);\n        }\n\n        // FIXME 段落边框与行内边框分离，行内只有全边框，段落分四边\n\n        // 背景色\n        String backgroundColor = getPropertyValue(HtmlConstants.CSS_BACKGROUND_COLOR, true);\n        if (StringUtils.isNotBlank(backgroundColor)) {\n            String hex = Colors.fromStyle(backgroundColor, null);\n            if (hex != null) {\n                CTShd ctShd = rPr.addNewShd();\n                ctShd.setFill(hex);\n                ctShd.setVal(STShd.CLEAR);\n            }\n        }\n\n        // 可见性\n        String visibility = getPropertyValue(HtmlConstants.CSS_VISIBILITY);\n        if (HtmlConstants.HIDDEN.equals(visibility) || HtmlConstants.COLLAPSE.equals(visibility)) {\n            rPr.addNewVanish();\n        }\n    }\n\n    /**\n     * 渲染图片\n     *\n     * @param pictureData 图片数据流\n     * @param pictureType 图片类型\n     * @param filename 文件名\n     * @param width 宽度\n     * @param height 高度\n     * @param svgData SVG数据\n     */\n    public void renderPicture(InputStream pictureData, int pictureType, String filename, int width, int height, byte[] svgData)\n            throws IOException, InvalidFormatException {\n        CTR ctr = newRun();\n\n        XWPFPicture xwpfPicture = currentRun.addPicture(pictureData, pictureType, filename, width, height);\n        CTR r = currentRun.getCTR();\n\n        boolean isSvg = svgData != null;\n        if (isSvg) {\n            attachSvgData(xwpfPicture, svgData);\n        }\n\n        CSSStyleDeclarationImpl styleDeclaration = currentElementStyle();\n        String cssFloat = styleDeclaration.getPropertyValue(HtmlConstants.CSS_FLOAT);\n        boolean floatLeft = HtmlConstants.LEFT.equals(cssFloat);\n        boolean floatRight = !floatLeft && HtmlConstants.RIGHT.equals(cssFloat);\n        boolean floatCenter = !floatLeft && !floatRight\n                && HtmlConstants.AUTO.equals(styleDeclaration.getPropertyValue(HtmlConstants.CSS_MARGIN_LEFT))\n                && HtmlConstants.AUTO.equals(styleDeclaration.getPropertyValue(HtmlConstants.CSS_MARGIN_RIGHT));\n        // vertical-align seems not working\n        boolean floated = floatLeft || floatRight || floatCenter;\n\n        CTDrawing drawing = null;\n        if (r != ctr) {\n            int lastDrawingIndex = r.sizeOfDrawingArray() - 1;\n            drawing = r.getDrawingArray(lastDrawingIndex);\n            ctr.setDrawingArray(new CTDrawing[]{drawing});\n            r.removeDrawing(lastDrawingIndex);\n            drawing = ctr.getDrawingArray(0);\n        } else if (isSvg || floated) {\n            drawing = ctr.getDrawingArray(ctr.sizeOfDrawingArray() - 1);\n        }\n        previousDrawingRun = ctr;\n\n        if (drawing != null && drawing.sizeOfInlineArray() > 0) {\n            if (floated) {\n                previousDrawingRun = null;\n                CTAnchor ctAnchor = RenderUtils.inlineToAnchor(drawing);\n                CTPosH ctPosH = ctAnchor.addNewPositionH();\n                ctPosH.setRelativeFrom(STRelFromH.MARGIN);\n\n                if (floatCenter) {\n                    moveContentToNewPrevParagraph(ctr);\n                    ctAnchor.addNewWrapTopAndBottom();\n                    ctPosH.setAlign(STAlignH.CENTER);\n                } else {\n                    ctAnchor.addNewWrapSquare().setWrapText(STWrapText.LARGEST);\n                    ctPosH.setAlign(floatRight ? STAlignH.RIGHT : STAlignH.LEFT);\n                }\n\n                CTPosV ctPosV = ctAnchor.addNewPositionV();\n                ctPosV.setRelativeFrom(STRelFromV.PARAGRAPH);\n                ctPosV.setAlign(STAlignV.TOP);\n\n                if (isSvg) {\n                    CTNonVisualGraphicFrameProperties properties = ctAnchor.addNewCNvGraphicFramePr();\n                    CTGraphicalObjectFrameLocking frameLocking = properties.addNewGraphicFrameLocks();\n                    frameLocking.setNoChangeAspect(true);\n                }\n            } else if (isSvg) {\n                CTInline ctInline = drawing.getInlineArray(0);\n                CTNonVisualGraphicFrameProperties properties = ctInline.isSetCNvGraphicFramePr()\n                        ? ctInline.getCNvGraphicFramePr() : ctInline.addNewCNvGraphicFramePr();\n                CTGraphicalObjectFrameLocking frameLocking = properties.isSetGraphicFrameLocks()\n                        ? properties.getGraphicFrameLocks() : properties.addNewGraphicFrameLocks();\n                frameLocking.setNoChangeAspect(true);\n            }\n        }\n    }\n\n    /**\n     * 附加SVG数据\n     *\n     * @param xwpfPicture 图片\n     * @param svgData SVG数据\n     * @throws InvalidFormatException 非法格式\n     */\n    private void attachSvgData(XWPFPicture xwpfPicture, byte[] svgData) throws InvalidFormatException {\n        CTPicture ctPicture = xwpfPicture.getCTPicture();\n        String svgRelId = getXWPFDocument().addPictureData(svgData, SVGPictureData.PICTURE_TYPE_SVG);\n        CTBlip blip = ctPicture.getBlipFill().getBlip();\n        if (blip != null) {\n            CTOfficeArtExtensionList extList = blip.isSetExtLst() ? blip.getExtLst() : blip.addNewExtLst();\n            CTOfficeArtExtension svgBitmap = extList.addNewExt();\n            svgBitmap.setUri(SVGRelation.SVG_URI);\n            XmlCursor cur = svgBitmap.newCursor();\n            cur.toEndToken();\n            cur.beginElement(SVGRelation.SVG_QNAME);\n            cur.insertNamespace(SVGRelation.SVG_PREFIX, SVGRelation.MS_SVG_NS);\n            cur.insertAttributeWithValue(SVGRelation.EMBED_TAG, svgRelId);\n            cur.dispose();\n        }\n    }\n\n    /**\n     * 将长度换算为EMU\n     *\n     * @param length 长度\n     * @return EMU\n     */\n    public int lengthToEMU(CSSLength length) {\n        if (!length.isValid()) {\n            throw new UnsupportedOperationException(\"Invalid CSS length\");\n        }\n        if (!length.getUnit().isRelative()) {\n            return length.toEMU();\n        }\n        double emu;\n        switch (length.getUnit()) {\n            case REM:\n                emu = length.unitValue() * getDefaultFontSize().toEMU();\n                break;\n            case EM:\n                emu = length.unitValue() * getInheritedFontSizeInHalfPoints() * Units.EMU_PER_POINT / 2;\n                break;\n            case VW:\n                emu = length.unitValue() * getPageWidth().toEMU();\n                break;\n            case VH:\n                emu = length.unitValue() * getPageHeight().toEMU();\n                break;\n            case VMIN:\n                emu = length.unitValue() * Math.min(getPageWidth().toEMU(), getPageHeight().toEMU());\n                break;\n            case VMAX:\n                emu = length.unitValue() * Math.max(getPageWidth().toEMU(), getPageHeight().toEMU());\n                break;\n            // Unable to determine the use of width or height as a relative length for percent unit\n            default:\n                throw new UnsupportedOperationException(\"Can not convert to EMU with length: \" + length);\n        }\n        return (int) Math.rint(emu);\n    }\n\n    public NumberingContext getNumberingContext() {\n        return numberingContext;\n    }\n\n    public CSSLength getPageWidth() {\n        return pageWidth;\n    }\n\n    public CSSLength getPageHeight() {\n        return pageHeight;\n    }\n\n    public CSSLength getMarginTop() {\n        return marginTop;\n    }\n\n    public CSSLength getMarginRight() {\n        return marginRight;\n    }\n\n    public CSSLength getMarginBottom() {\n        return marginBottom;\n    }\n\n    public CSSLength getMarginLeft() {\n        return marginLeft;\n    }\n\n    public int getAvailablePageWidth() {\n        return availablePageWidth;\n    }\n\n    public int getAvailablePageHeight() {\n        return availablePageHeight;\n    }\n\n    public MathRenderConfig getMathRenderConfig() {\n        return mathRenderConfig;\n    }\n\n    public void setMathRenderConfig(MathRenderConfig mathRenderConfig) {\n        this.mathRenderConfig = mathRenderConfig;\n    }\n\n    public XWPFRun getCurrentRun() {\n        return currentRun;\n    }\n\n    public String getGlobalFont() {\n        return globalFont;\n    }\n\n    public BigInteger getGlobalFontSize() {\n        return globalFontSize;\n    }\n\n    public boolean isShowDefaultTableBorderInTableCell() {\n        return showDefaultTableBorderInTableCell;\n    }\n\n    public void setShowDefaultTableBorderInTableCell(boolean showDefaultTableBorderInTableCell) {\n        this.showDefaultTableBorderInTableCell = showDefaultTableBorderInTableCell;\n    }\n\n    public void setGlobalFont(String globalFont) {\n        this.globalFont = globalFont;\n    }\n\n    public void setGlobalFontSize(BigInteger globalFontSize) {\n        this.globalFontSize = globalFontSize;\n    }\n\n    public boolean isBlocked() {\n        return blockLevel > 0;\n    }\n\n    public void incrementBlockLevel() {\n        blockLevel++;\n    }\n\n    public void decrementBlockLevel() {\n        blockLevel--;\n    }\n\n    public void renderDocument(Document document) {\n        Element body = document.body();\n        Element html = body.parent();\n        if (html.hasAttr(HtmlConstants.ATTR_STYLE)) {\n            pushInlineStyle(getCssStyleDeclaration(html), html.isBlock());\n        }\n        if (body.hasAttr(HtmlConstants.ATTR_STYLE)) {\n            pushInlineStyle(getCssStyleDeclaration(body), body.isBlock());\n        }\n        for (Node node : body.childNodes()) {\n            renderNode(node);\n        }\n        globalCursor.dispose();\n    }\n\n    public void renderNode(Node node) {\n        boolean isElement = node instanceof Element;\n\n        if (isElement) {\n            Element element = ((Element) node);\n            renderElement(element);\n        } else if (node instanceof TextNode) {\n            renderText(((TextNode) node).getWholeText());\n        }\n    }\n\n    public void renderElement(Element element) {\n        if (log.isDebugEnabled()) {\n            log.info(\"Start rendering html tag: <{}{}>\", element.normalName(), element.attributes());\n        }\n        if (element.tag().isFormListed() || element.tag().isFormSubmittable()) {\n            return;\n        }\n\n        CSSStyleDeclarationImpl cssStyleDeclaration = getCssStyleDeclaration(element);\n        String display = cssStyleDeclaration.getPropertyValue(HtmlConstants.CSS_DISPLAY);\n        if (HtmlConstants.NONE.equalsIgnoreCase(display)) {\n            return;\n        }\n        pushInlineStyle(cssStyleDeclaration, element.isBlock());\n\n        ElementRenderer elementRenderer = rendererProvider.get(element.normalName());\n        boolean blocked = false;\n\n        if (renderAsBlock(element, elementRenderer)) {\n            if (element.childNodeSize() == 0 && !HtmlConstants.KEEP_EMPTY_TAGS.contains(element.normalName())) {\n                popInlineStyle();\n                return;\n            }\n            if (!isBlocked()) {\n                // 复制段落中占位符之前的部分内容\n                moveContentToNewPrevParagraph(getRun().getCTR());\n            }\n            incrementBlockLevel();\n            blocked = true;\n\n            IBody container = getContainer();\n            boolean isTableTag = HtmlConstants.TAG_TABLE.equals(element.normalName());\n\n            adjustCursor(container, isTableTag);\n\n            if (isTableTag) {\n                globalCursor.push();\n                XWPFTable xwpfTable = container.insertNewTbl(globalCursor);\n                globalCursor.pop();\n                if (dedupeParagraph != null && !numberingContext.contains(dedupeParagraph)) {\n                    if (!dedupeParagraph.equals(getRun().getParent()) && isEmptyParagraph(dedupeParagraph)) {\n                        removeParagraph(container, dedupeParagraph);\n                    }\n                    unmarkDedupe();\n                }\n                // 新增时会自动创建一行一列，会影响自定义的表格渲染逻辑，故删除\n                xwpfTable.removeRow(0);\n\n                if (container.getPartType() == BodyType.TABLECELL && isShowDefaultTableBorderInTableCell()) {\n                    CTTbl ctTbl = xwpfTable.getCTTbl();\n                    CTTblBorders tblBorders = RenderUtils.getTblBorders(ctTbl);\n                    tblBorders.addNewTop().setVal(STBorder.SINGLE);\n                    tblBorders.addNewLeft().setVal(STBorder.SINGLE);\n                    tblBorders.addNewBottom().setVal(STBorder.SINGLE);\n                    tblBorders.addNewRight().setVal(STBorder.SINGLE);\n                    tblBorders.addNewInsideH().setVal(STBorder.SINGLE);\n                    tblBorders.addNewInsideV().setVal(STBorder.SINGLE);\n                }\n\n                RenderUtils.tableStyle(this, xwpfTable, cssStyleDeclaration);\n            } else if (shouldNewParagraph(element)) {\n                globalCursor.push();\n                XWPFParagraph xwpfParagraph = newParagraph(container, globalCursor);\n                globalCursor.pop();\n                if (xwpfParagraph == null) {\n                    log.warn(\"Can not add new paragraph for element: {}, attributes: {}\", element.tagName(), element.attributes().html());\n                }\n\n                RenderUtils.paragraphStyle(this, xwpfParagraph, cssStyleDeclaration);\n            } else {\n                RenderUtils.paragraphStyle(this, dedupeParagraph, cssStyleDeclaration);\n            }\n        }\n\n        if (elementRenderer != null) {\n            if (!elementRenderer.renderStart(element, this)) {\n                renderElementEnd(element, this, elementRenderer, blocked);\n                return;\n            }\n        }\n\n        for (Node child : element.childNodes()) {\n            renderNode(child);\n        }\n\n        renderElementEnd(element, this, elementRenderer, blocked);\n    }\n\n    private boolean isEmptyParagraph(XWPFParagraph paragraph) {\n        for (XWPFRun run : paragraph.getRuns()) {\n            if (StringUtils.isNotBlank(run.text())) {\n                return false;\n            }\n            if (!run.getEmbeddedPictures().isEmpty()) {\n                return false;\n            }\n        }\n        CTP ctp = paragraph.getCTP();\n        return ctp.sizeOfOMathArray() == 0 && ctp.sizeOfOMathParaArray() == 0;\n    }\n\n    private void removeParagraph(IBody container, XWPFParagraph paragraph) {\n        switch (container.getPartType()) {\n            case CONTENTCONTROL:\n                break;\n            case DOCUMENT:\n                XWPFDocument xwpfDocument = (XWPFDocument) container;\n                int posOfParagraph = xwpfDocument.getPosOfParagraph(paragraph);\n                xwpfDocument.removeBodyElement(posOfParagraph);\n                break;\n            case HEADER:\n                XWPFHeader xwpfHeader = (XWPFHeader) container;\n                xwpfHeader.removeParagraph(paragraph);\n                break;\n            case FOOTER:\n                XWPFFooter xwpfFooter = (XWPFFooter) container;\n                xwpfFooter.removeParagraph(paragraph);\n                break;\n            case FOOTNOTE:\n                XWPFFootnote xwpfFootnote = (XWPFFootnote) container;\n                xwpfFootnote.getParagraphs().remove(paragraph);\n                break;\n            case TABLECELL:\n                XWPFTableCell xwpfTableCell = (XWPFTableCell) container;\n                xwpfTableCell.removeParagraph(xwpfTableCell.getParagraphs().indexOf(paragraph));\n                break;\n        }\n    }\n\n    /**\n     * HTML元素是否按照块状进行渲染\n     *\n     * @param element HTML元素\n     * @return 是否按照块状进行渲染\n     */\n    public boolean renderAsBlock(Element element) {\n        return renderAsBlock(element, rendererProvider.get(element.normalName()));\n    }\n\n    /**\n     * HTML元素是否按照块状进行渲染\n     *\n     * @param element HTML元素\n     * @param elementRenderer 元素渲染器\n     * @return 是否按照块状进行渲染\n     */\n    private boolean renderAsBlock(Element element, ElementRenderer elementRenderer) {\n        return element.isBlock() && (elementRenderer == null || elementRenderer.renderAsBlock());\n    }\n\n    private boolean shouldNewParagraph(Element element) {\n        if (dedupeParagraph == null) {\n            return true;\n            // return !HtmlConstants.TAG_HR.equals(element.normalName());\n        }\n        boolean newParagraph = false;\n        XmlCursor xmlCursor = dedupeParagraph.getCTP().newCursor();\n        xmlCursor.push();\n        if (xmlCursor.toPrevSibling()) {\n            if (XmlUtils.P_QNAME.equals(xmlCursor.getName())) {\n                newParagraph = removeLastBrRun(xmlCursor);\n            }\n        }\n\n        if (!newParagraph) {\n            xmlCursor.pop();\n            newParagraph = removeLastBrRun(xmlCursor);\n        }\n\n        xmlCursor.dispose();\n        return newParagraph;\n    }\n\n    private boolean removeLastBrRun(XmlCursor xmlCursor) {\n        boolean removed = false;\n        if (xmlCursor.toLastChild()) {\n            if (XmlUtils.R_QNAME.equals(xmlCursor.getName())) {\n                xmlCursor.push();\n                if (xmlCursor.toFirstChild() && XmlUtils.BR_QNAME.equals(xmlCursor.getName()) && !xmlCursor.toNextSibling()) {\n                    xmlCursor.pop();\n                    xmlCursor.removeXml();\n                    unmarkDedupe();\n                    removed = true;\n                } else {\n                    xmlCursor.pop();\n                }\n            }\n        }\n        return removed;\n    }\n\n    private void adjustCursor(IBody container, boolean isTableTag) {\n        if (XmlUtils.R_QNAME.equals(globalCursor.getName())) {\n            globalCursor.push();\n            globalCursor.toParent();\n        }\n        globalCursor.push();\n        // 如果是表格，检查当前word容器的前一个兄弟元素是否为表格，是则插入一个段落，防止表格粘连在一起\n        if (isTableTag && globalCursor.toPrevSibling()) {\n            if (XmlUtils.TBL_QNAME.equals(globalCursor.getName())) {\n                // pop() is safer than toNextSibling()\n                globalCursor.pop();\n                globalCursor.push();\n                XWPFParagraph paragraph = newParagraph(container, globalCursor);\n                unmarkDedupe();\n                RenderUtils.paragraphStyle(this, paragraph, CSSStyleUtils.EMPTY_STYLE);\n            }\n        }\n        globalCursor.pop();\n    }\n\n    private void renderElementEnd(Element element, HtmlRenderContext context, ElementRenderer elementRenderer, boolean blocked) {\n        if (elementRenderer != null) {\n            elementRenderer.renderEnd(element, context);\n        }\n        context.popInlineStyle();\n        if (blocked) {\n            context.decrementBlockLevel();\n        }\n    }\n\n    private void moveContentToNewPrevParagraph(CTR ctr) {\n        XmlCursor rCursor = ctr.newCursor();\n        boolean hasPrevSibling = false;\n        while (rCursor.toPrevSibling()) {\n            XmlObject object = rCursor.getObject();\n            if (object instanceof CTMarkupRange) {\n                continue;\n            }\n            if (!XmlUtils.PPR_QNAME.equals(rCursor.getName())) {\n                hasPrevSibling = true;\n                break;\n            }\n        }\n        if (!hasPrevSibling) {\n            rCursor.dispose();\n            return;\n        }\n        rCursor.toParent();\n        rCursor.push();\n        CTP ctp = ((CTP) rCursor.getObject());\n        XWPFParagraph paragraph = getContainer().getParagraph(ctp);\n        XWPFParagraph newParagraph = getContainer().insertNewParagraph(rCursor);\n        XmlCursor pCursor = newParagraph.getCTP().newCursor();\n        pCursor.toEndToken();\n        rCursor.pop();\n        rCursor.toFirstChild();\n\n        XmlObject previousRun = null;\n        if (previousText != null) {\n            XmlCursor tCursor = previousText.getText().newCursor();\n            tCursor.toParent();\n            previousRun = tCursor.getObject();\n            tCursor.dispose();\n        }\n\n        while (true) {\n            XmlObject object = rCursor.getObject();\n            if (ctr == object) break;\n            QName name = rCursor.getName();\n            if (XmlUtils.PPR_QNAME.equals(name)) {\n                rCursor.copyXml(pCursor);\n                rCursor.toNextSibling();\n            } else if (XmlUtils.BOOKMARK_START_QNAME.equals(name) || XmlUtils.BOOKMARK_END_QNAME.equals(name)) {\n                rCursor.toNextSibling();\n            } else {\n                if (previousDrawingRun == object) {\n                    previousDrawingRun = null;\n                }\n                if (previousRun == object) {\n                    previousText = null;\n                }\n                // moveXml附带了toNextSibling的效果\n                rCursor.moveXml(pCursor);\n            }\n        }\n        rCursor.dispose();\n        pCursor.dispose();\n\n        XWPFParagraphRuns runs = new XWPFParagraphRuns(paragraph);\n\n        for (int i = runs.runCount() - ctp.getRList().size() - 1; i >= 0; i--) {\n            runs.remove(i);\n        }\n    }\n\n    public CSSStyleDeclarationImpl getCssStyleDeclaration(Element element) {\n        String style = element.attr(HtmlConstants.ATTR_STYLE);\n        CSSStyleDeclarationImpl cssStyleDeclaration = CSSStyleUtils.parse(style);\n        CSSStyleUtils.split(cssStyleDeclaration);\n        return cssStyleDeclaration;\n    }\n\n    /**\n     * 保存当前指针位置并移动到目标指针位置\n     *\n     * @param targetCursor 目标指针\n     */\n    public void pushCursor(XmlCursor targetCursor) {\n        globalCursor.push();\n        globalCursor.toCursor(targetCursor);\n    }\n\n    /**\n     * 返回之前保存的指针位置\n     *\n     * @return 是否返回成功\n     */\n    public boolean popCursor() {\n        return globalCursor.pop();\n    }\n\n    /**\n     * @return 指针当前指向的对象\n     */\n    public XmlObject currentCursorObject() {\n        return globalCursor.getObject();\n    }\n\n    /**\n     * 标记段落以防止块状元素嵌套产生多余的空段落\n     *\n     * @param paragraph 段落\n     */\n    public void markDedupe(XWPFParagraph paragraph) {\n        dedupeParagraph = paragraph;\n    }\n\n    /**\n     * 取消段落防重标记\n     */\n    public void unmarkDedupe() {\n        dedupeParagraph = null;\n    }\n\n    /**\n     * 文本封装类，用于空白字符折叠处理\n     */\n    private static class TextWrapper {\n        private final CTText text;\n        private final boolean endTrimmed;\n\n        public TextWrapper(CTText text, boolean endTrimmed) {\n            this.text = text;\n            this.endTrimmed = endTrimmed;\n        }\n\n        public CTText getText() {\n            return text;\n        }\n\n        public boolean isEndTrimmed() {\n            return endTrimmed;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/HtmlRenderPolicy.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html;\n\nimport com.deepoove.poi.policy.AbstractRenderPolicy;\nimport com.deepoove.poi.render.RenderContext;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.poi.xwpf.usermodel.BodyElementType;\nimport org.apache.poi.xwpf.usermodel.BodyType;\nimport org.apache.poi.xwpf.usermodel.IBody;\nimport org.apache.poi.xwpf.usermodel.IBodyElement;\nimport org.apache.poi.xwpf.usermodel.XWPFRun;\nimport org.apache.poi.xwpf.usermodel.XWPFTableCell;\nimport org.apache.xmlbeans.XmlCursor;\nimport org.ddr.poi.html.tag.ARenderer;\nimport org.ddr.poi.html.tag.BigRenderer;\nimport org.ddr.poi.html.tag.BoldRenderer;\nimport org.ddr.poi.html.tag.BreakRenderer;\nimport org.ddr.poi.html.tag.DeleteRenderer;\nimport org.ddr.poi.html.tag.FigureCaptionRenderer;\nimport org.ddr.poi.html.tag.FigureRenderer;\nimport org.ddr.poi.html.tag.HeaderBreakRenderer;\nimport org.ddr.poi.html.tag.HeaderRenderer;\nimport org.ddr.poi.html.tag.ImageRenderer;\nimport org.ddr.poi.html.tag.ItalicRenderer;\nimport org.ddr.poi.html.tag.LaTeXRenderer;\nimport org.ddr.poi.html.tag.ListItemRenderer;\nimport org.ddr.poi.html.tag.ListRenderer;\nimport org.ddr.poi.html.tag.MarkRenderer;\nimport org.ddr.poi.html.tag.MathRenderer;\nimport org.ddr.poi.html.tag.OmittedRenderer;\nimport org.ddr.poi.html.tag.PreRenderer;\nimport org.ddr.poi.html.tag.RubyRenderer;\nimport org.ddr.poi.html.tag.SmallRenderer;\nimport org.ddr.poi.html.tag.SubscriptRenderer;\nimport org.ddr.poi.html.tag.SuperscriptRenderer;\nimport org.ddr.poi.html.tag.SvgRenderer;\nimport org.ddr.poi.html.tag.TableCellRenderer;\nimport org.ddr.poi.html.tag.TableRenderer;\nimport org.ddr.poi.html.tag.UnderlineRenderer;\nimport org.ddr.poi.html.tag.WalkThroughRenderer;\nimport org.ddr.poi.html.util.CSSLength;\nimport org.ddr.poi.html.util.JsoupUtils;\nimport org.ddr.poi.util.XmlUtils;\nimport org.jsoup.nodes.Document;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;\n\nimport java.math.BigInteger;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * HTML字符串渲染策略\n *\n * @author Draco\n * @since 2021-02-07\n */\npublic class HtmlRenderPolicy extends AbstractRenderPolicy<String> {\n    private final Map<String, ElementRenderer> elRenderers;\n    private final HtmlRenderConfig config;\n\n    public HtmlRenderPolicy() {\n        this(new HtmlRenderConfig());\n    }\n\n    @Deprecated\n    public HtmlRenderPolicy(String globalFont, CSSLength globalFontSize) {\n        this(new HtmlRenderConfig());\n        config.setGlobalFont(globalFont);\n        config.setGlobalFontSize(globalFontSize);\n    }\n\n    public HtmlRenderPolicy(HtmlRenderConfig config) {\n        ElementRenderer[] renderers = {\n                new ARenderer(),\n                new BigRenderer(),\n                new BoldRenderer(),\n                new BreakRenderer(),\n                new DeleteRenderer(),\n                new FigureRenderer(),\n                new FigureCaptionRenderer(),\n                new HeaderBreakRenderer(),\n                new HeaderRenderer(),\n                new ImageRenderer(),\n                new ItalicRenderer(),\n                new LaTeXRenderer(),\n                new ListItemRenderer(),\n                new ListRenderer(),\n                new MarkRenderer(),\n                new MathRenderer(),\n                new OmittedRenderer(),\n                new PreRenderer(),\n                new RubyRenderer(),\n                new SmallRenderer(),\n                new SubscriptRenderer(),\n                new SuperscriptRenderer(),\n                new SvgRenderer(),\n                new TableCellRenderer(),\n                new TableRenderer(),\n                new UnderlineRenderer(),\n                new WalkThroughRenderer()\n        };\n        elRenderers = new HashMap<>(renderers.length);\n        for (ElementRenderer renderer : renderers) {\n            for (String tag : renderer.supportedTags()) {\n                elRenderers.put(tag, renderer);\n            }\n        }\n        this.config = config;\n        // custom tag renderer will overwrite the built-in renderer\n        if (config.getCustomRenderers() != null) {\n            for (ElementRenderer customRenderer : config.getCustomRenderers()) {\n                for (String tag : customRenderer.supportedTags()) {\n                    elRenderers.put(tag, customRenderer);\n                }\n            }\n        }\n    }\n\n    public HtmlRenderConfig getConfig() {\n        return config;\n    }\n\n    @Override\n    protected boolean validate(String data) {\n        return StringUtils.isNotEmpty(data);\n    }\n\n    @Override\n    public void doRender(RenderContext<String> context) throws Exception {\n        Document document = JsoupUtils.parse(context.getData());\n        document.outputSettings().prettyPrint(false).indentAmount(0);\n\n        HtmlRenderContext htmlRenderContext = new HtmlRenderContext(context, elRenderers::get);\n        htmlRenderContext.setGlobalFont(config.getGlobalFont());\n        if (config.getGlobalFontSizeInHalfPoints() > 0) {\n            htmlRenderContext.setGlobalFontSize(BigInteger.valueOf(config.getGlobalFontSizeInHalfPoints()));\n        }\n        htmlRenderContext.getNumberingContext().setIndent(config.getNumberingIndent());\n        htmlRenderContext.getNumberingContext().setHanging(config.getNumberingHanging());\n        htmlRenderContext.getNumberingContext().setSpacing(config.getNumberingSpacing());\n        htmlRenderContext.setShowDefaultTableBorderInTableCell(config.isShowDefaultTableBorderInTableCell());\n        htmlRenderContext.setMathRenderConfig(config.getMathRenderConfig());\n\n        htmlRenderContext.renderDocument(document);\n    }\n\n    @Override\n    protected void afterRender(RenderContext<String> context) {\n        boolean hasSibling = hasSibling(context.getRun());\n        clearPlaceholder(context, !hasSibling);\n\n        IBody container = context.getContainer();\n        if (container.getPartType() == BodyType.TABLECELL) {\n            // 单元格的最后一个元素应为p，否则可能无法正常打开文件\n            List<IBodyElement> bodyElements = container.getBodyElements();\n            if (bodyElements.isEmpty() || bodyElements.get(bodyElements.size() - 1).getElementType() != BodyElementType.PARAGRAPH) {\n                ((XWPFTableCell) container).addParagraph();\n            }\n        }\n    }\n\n    private boolean hasSibling(XWPFRun run) {\n        boolean hasSibling = false;\n        CTR ctr = run.getCTR();\n        XmlCursor xmlCursor = ctr.newCursor();\n        xmlCursor.push();\n        while (xmlCursor.toNextSibling()) {\n            if (isValidSibling(xmlCursor)) {\n                hasSibling = true;\n                break;\n            }\n        }\n        if (!hasSibling) {\n            xmlCursor.pop();\n            while (xmlCursor.toPrevSibling()) {\n                if (isValidSibling(xmlCursor)) {\n                    hasSibling = true;\n                    break;\n                }\n            }\n        }\n        xmlCursor.dispose();\n        return hasSibling;\n    }\n\n    private boolean isValidSibling(XmlCursor cursor) {\n        return !XmlUtils.INVALID_R_SIBLINGS.contains(cursor.getName());\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/ARenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.jsoup.nodes.Element;\n\n/**\n * a标签渲染器\n *\n * @author Draco\n * @since 2021-03-31\n */\npublic class ARenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_A};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        String href = element.attr(HtmlConstants.ATTR_HREF);\n        context.startHyperlink(href);\n        return true;\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.endHyperlink();\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/BigRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.CSSStyleUtils;\nimport org.jsoup.nodes.Element;\n\n/**\n * big标签渲染器，HTML5不支持big\n *\n * @author Draco\n * @since 2021-02-23\n */\n@Deprecated\npublic class BigRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_BIG};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        context.pushInlineStyle(CSSStyleUtils.parse(HtmlConstants.DEFINED_LARGER), element.isBlock());\n        return true;\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.popInlineStyle();\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/BoldRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.CSSStyleUtils;\nimport org.jsoup.nodes.Element;\n\n/**\n * 加粗标签渲染器\n *\n * @author Draco\n * @since 2021-02-23\n */\npublic class BoldRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_B, HtmlConstants.TAG_STRONG};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        context.pushInlineStyle(CSSStyleUtils.parse(HtmlConstants.DEFINED_BOLD), element.isBlock());\n        return true;\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.popInlineStyle();\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/BreakRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.apache.poi.xwpf.usermodel.IRunBody;\nimport org.apache.poi.xwpf.usermodel.XWPFParagraph;\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.jsoup.nodes.Element;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;\n\n/**\n * br标签渲染器\n *\n * @author Draco\n * @since 2021-02-09\n */\npublic class BreakRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_BR};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        CTR ctr = context.newRun();\n        ctr.addNewBr();\n        IRunBody parent = context.getCurrentRun().getParent();\n        if (parent instanceof XWPFParagraph) {\n            context.markDedupe((XWPFParagraph) parent);\n        }\n        return false;\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/DeleteRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.CSSStyleUtils;\nimport org.jsoup.nodes.Element;\n\n/**\n * 删除线标签渲染器\n *\n * @author Draco\n * @since 2021-02-23\n */\npublic class DeleteRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_S, HtmlConstants.TAG_DEL, HtmlConstants.TAG_STRIKE};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        context.pushInlineStyle(CSSStyleUtils.parse(HtmlConstants.DEFINED_STRIKE), element.isBlock());\n        return true;\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.popInlineStyle();\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/FigureCaptionRenderer.java",
    "content": "package org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.jsoup.nodes.Element;\n\n/**\n * figcaption标签渲染器\n *\n * @author Draco\n * @since 2022-11-03\n */\npublic class FigureCaptionRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_FIGURE_CAPTION};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        context.markDedupe(context.getClosestParagraph());\n        return true;\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.unmarkDedupe();\n    }\n\n    /**\n     * @return 支持的HTML标签\n     */\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    /**\n     * @return 是否为块状渲染，如果为true在Word中会另起一个Paragraph\n     */\n    @Override\n    public boolean renderAsBlock() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/FigureRenderer.java",
    "content": "package org.ddr.poi.html.tag;\n\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\nimport org.apache.poi.xwpf.usermodel.ParagraphAlignment;\nimport org.apache.poi.xwpf.usermodel.XWPFParagraph;\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.JsoupUtils;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.select.Elements;\n\n/**\n * figure标签渲染器\n *\n * @author Draco\n * @since 2022-11-03\n */\npublic class FigureRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_FIGURE};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure#usage_notes\n        Elements captions = JsoupUtils.children(element, HtmlConstants.TAG_FIGURE_CAPTION);\n        if (captions.size() > 1) {\n            captions.remove(0);\n            captions.remove();\n        }\n\n        XWPFParagraph paragraph = context.getClosestParagraph();\n        context.markDedupe(paragraph);\n\n        CSSStyleDeclarationImpl styleDeclaration = context.currentElementStyle();\n        String cssFloat = styleDeclaration.getPropertyValue(HtmlConstants.CSS_FLOAT);\n        if (HtmlConstants.LEFT.equals(cssFloat)) {\n            paragraph.setAlignment(ParagraphAlignment.LEFT);\n            styleDeclaration.setTextAlign(HtmlConstants.LEFT);\n        } else if (HtmlConstants.RIGHT.equals(cssFloat)) {\n            paragraph.setAlignment(ParagraphAlignment.RIGHT);\n            styleDeclaration.setTextAlign(HtmlConstants.RIGHT);\n        } else {\n            paragraph.setAlignment(ParagraphAlignment.CENTER);\n            styleDeclaration.setTextAlign(HtmlConstants.CENTER);\n        }\n\n        return true;\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.unmarkDedupe();\n    }\n\n    /**\n     * @return 支持的HTML标签\n     */\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    /**\n     * @return 是否为块状渲染，如果为true在Word中会另起一个Paragraph\n     */\n    @Override\n    public boolean renderAsBlock() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/HeaderBreakRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.RenderUtils;\nimport org.jsoup.nodes.Element;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBorder;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPBdr;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder;\n\nimport java.math.BigInteger;\n\n/**\n * hr标签渲染器\n *\n * @author Draco\n * @since 2021-02-18\n */\npublic class HeaderBreakRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_HR};\n    /**\n     * 线粗细，相当于3px\n     */\n    private static final BigInteger SIZE = BigInteger.valueOf(6);\n    /**\n     * 间距\n     */\n    private static final BigInteger SPACE = BigInteger.ONE;\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        CTP ctp = context.getClosestParagraph().getCTP();\n        CTPBdr pBdr = RenderUtils.getPBdr(RenderUtils.getPPr(ctp));\n        CTBorder ctBorder = pBdr.addNewBottom();\n        ctBorder.setVal(STBorder.SINGLE);\n        ctBorder.setSz(SIZE);\n        ctBorder.setSpace(SPACE);\n\n        ctBorder = pBdr.addNewBetween();\n        ctBorder.setVal(STBorder.SINGLE);\n        ctBorder.setSz(SIZE);\n        ctBorder.setSpace(SPACE);\n        return false;\n    }\n\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.unmarkDedupe();\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/HeaderRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.CSSStyleUtils;\nimport org.ddr.poi.html.util.RenderUtils;\nimport org.jsoup.nodes.Element;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;\n\nimport java.math.BigInteger;\n\n/**\n * h1~h6标签渲染器\n *\n * @author Draco\n * @since 2021-02-24\n */\npublic class HeaderRenderer implements ElementRenderer {\n    private static final String[] TAGS = {\n            HtmlConstants.TAG_H1, HtmlConstants.TAG_H2, HtmlConstants.TAG_H3,\n            HtmlConstants.TAG_H4, HtmlConstants.TAG_H5, HtmlConstants.TAG_H6\n    };\n\n    /**\n     * 各级别标题对应字号\n     */\n    private static final String[] FONT_SIZES = {\"24pt\", \"18pt\", \"14pt\", \"12pt\", \"10pt\", \"7.5pt\"};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        int index = Integer.parseInt(element.normalName().substring(1)) - 1;\n        String fontSizeStyle = HtmlConstants.inlineStyle(HtmlConstants.CSS_FONT_SIZE, FONT_SIZES[index]);\n        context.pushInlineStyle(CSSStyleUtils.parse(HtmlConstants.DEFINED_BOLD + fontSizeStyle), element.isBlock());\n\n        CTP ctp = context.getClosestParagraph().getCTP();\n        CTDecimalNumber ctDecimalNumber = CTDecimalNumber.Factory.newInstance();\n        ctDecimalNumber.setVal(BigInteger.valueOf(index));\n        RenderUtils.getPPr(ctp).setOutlineLvl(ctDecimalNumber);\n        return true;\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.popInlineStyle();\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/ImageRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport com.drew.imaging.FileType;\nimport com.drew.imaging.FileTypeDetector;\nimport com.drew.imaging.ImageMetadataReader;\nimport com.drew.imaging.ImageProcessingException;\nimport com.drew.metadata.Metadata;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.math.NumberUtils;\nimport org.apache.poi.openxml4j.exceptions.InvalidFormatException;\nimport org.apache.poi.util.Units;\nimport org.apache.poi.xwpf.usermodel.SVGPictureData;\nimport org.ddr.image.ImageInfo;\nimport org.ddr.image.ImageType;\nimport org.ddr.image.MetadataReader;\nimport org.ddr.image.MetadataReaders;\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.CSSLength;\nimport org.ddr.poi.math.MathMLUtils;\nimport org.ddr.poi.util.ByteArrayCopyStream;\nimport org.ddr.poi.util.HttpURLConnectionUtils;\nimport org.jsoup.nodes.Element;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.imageio.ImageIO;\nimport javax.imageio.ImageReader;\nimport java.awt.*;\nimport java.awt.image.BufferedImage;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.UnsupportedEncodingException;\nimport java.net.HttpURLConnection;\nimport java.net.URLDecoder;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.Iterator;\n\n/**\n * img标签渲染器\n *\n * @author Draco\n * @since 2021-02-09\n */\npublic class ImageRenderer implements ElementRenderer {\n    private static final Logger log = LoggerFactory.getLogger(ImageRenderer.class);\n\n    private static final String[] TAGS = {HtmlConstants.TAG_IMG};\n    private static final String HTTP = \"http\";\n    private static final String DOUBLE_SLASH = \"//\";\n    private static final String DATA_PREFIX = \"data:\";\n    private static final String COMMENT_MATH_PREFIX = \"<!--MathML: <math \";\n    private static final String COMMENT_MATH_SUFFIX = \"</math>-->\";\n\n    static {\n        SVGPictureData.initRelation();\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        String src = element.attr(HtmlConstants.ATTR_SRC);\n        if (StringUtils.startsWithIgnoreCase(src, HTTP)) {\n            handleRemoteImage(element, context, src);\n        } else if (StringUtils.startsWith(src, DOUBLE_SLASH)) {\n            // 某些图片链接为了跟随网站协议而隐去了协议名称\n            handleRemoteImage(element, context, HTTP + HtmlConstants.COLON + src);\n        } else if (StringUtils.startsWith(src, DATA_PREFIX)) {\n            handleData(element, context, src);\n        }\n        return false;\n    }\n\n    /**\n     * 处理Data URL\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @param src 数据\n     */\n    private void handleData(Element element, HtmlRenderContext context, String src) {\n        int index = src.indexOf(HtmlConstants.COMMA.charAt(0));\n        String data = src.substring(index + 1);\n        String declaration = src.substring(0, index);\n        String format = StringUtils.substringBetween(declaration, HtmlConstants.SLASH, HtmlConstants.SEMICOLON);\n        // org.apache.poi.sl.usermodel.PictureData.PictureType\n        if (format.contains(HtmlConstants.MINUS)) {\n            format = StringUtils.substringAfterLast(format, HtmlConstants.MINUS);\n        } else if (format.contains(HtmlConstants.PLUS)) {\n            format = StringUtils.substringBefore(format, HtmlConstants.PLUS);\n        }\n\n        byte[] bytes;\n        if (declaration.contains(\"base64\")) {\n            try {\n                bytes = Base64.getDecoder().decode(data);\n            } catch (Exception e) {\n                log.warn(\"Failed to load image due to illegal base64 data: {}\", src);\n                return;\n            }\n        } else {\n            if (data.startsWith(HtmlConstants.PERCENT)) {\n                try {\n                    data = URLDecoder.decode(data, StandardCharsets.UTF_8.name());\n                } catch (UnsupportedEncodingException e) {\n                    log.warn(\"Failed to load image due to illegal data url: {}\", src);\n                    return;\n                }\n            }\n\n            // wiris support\n            int startOfMath = data.indexOf(COMMENT_MATH_PREFIX);\n            if (startOfMath >= 0) {\n                try {\n                    int endOfMath = data.indexOf(COMMENT_MATH_SUFFIX, startOfMath + COMMENT_MATH_PREFIX.length());\n                    String math = data.substring(startOfMath + 12, endOfMath + 7);\n                    MathMLUtils.renderTo(context.getClosestParagraph(), context.newRun(), math, context.getMathRenderConfig());\n                    return;\n                } catch (Exception e) {\n                    log.warn(\"Failed to render math in wiris svg, will try to render as svg image: {}\", data, e);\n                }\n            }\n\n            bytes = data.getBytes(StandardCharsets.UTF_8);\n        }\n        boolean svg = HtmlConstants.TAG_SVG.equals(format);\n        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {\n            ImageInfo info = analyzeImage(inputStream, svg);\n            if (info == null) {\n                log.warn(\"Illegal image url: {}\", src);\n                return;\n            }\n            addPicture(element, context, info.getStream(), info.getRawType(), info.getWidth(), info.getHeight(), svg ? bytes : null);\n        } catch (IOException | InvalidFormatException e) {\n            log.warn(\"Failed to load image: {}\", src, e);\n        }\n    }\n\n    /**\n     * 根据图片反推类型\n     *\n     * @param image 图片\n     * @return 图片类型\n     */\n    protected ImageType typeOf(BufferedImage image) {\n        return image.getColorModel().hasAlpha() ? ImageType.PNG : ImageType.JPG;\n    }\n\n    /**\n     * 处理远程图片\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @param src 图片链接地址\n     */\n    private void handleRemoteImage(Element element, HtmlRenderContext context, String src) {\n        HttpURLConnection connect = null;\n        try {\n            connect = HttpURLConnectionUtils.connect(src);\n            HttpURLConnectionUtils.initUserAgent(connect);\n            int firstSlashPosition = src.indexOf('/', src.indexOf(\"://\") + 3);\n            connect.setRequestProperty(\"Referrer\", src.substring(0, firstSlashPosition));\n\n            InputStream urlStream = connect.getInputStream();\n            boolean svg = StringUtils.contains(connect.getHeaderField(\"content-type\"), HtmlConstants.TAG_SVG);\n            ByteArrayCopyStream outputStream = new ByteArrayCopyStream(urlStream.available());\n            IOUtils.copy(urlStream, outputStream);\n            final byte[] svgData = svg ? outputStream.toByteArray() : null;\n\n            ByteArrayInputStream inputStream = outputStream.toInput();\n            ImageInfo info = analyzeImage(inputStream, svg);\n            if (info == null) {\n                log.warn(\"Illegal image url: {}\", src);\n                return;\n            }\n\n            addPicture(element, context, info.getStream(), info.getRawType(), info.getWidth(), info.getHeight(), svgData);\n        } catch (IOException | InvalidFormatException e) {\n            log.warn(\"Failed to load image: {}\", src, e);\n        } finally {\n            IOUtils.close(connect);\n        }\n    }\n\n    private ImageInfo analyzeImage(ByteArrayInputStream inputStream, boolean svg) throws IOException, InvalidFormatException {\n        final long length = inputStream.available();\n        // actual image data stream\n        ByteArrayInputStream stream = inputStream;\n        ImageType type = null;\n        Dimension dimension = null;\n\n        if (svg) {\n            BufferedImage image = ImageIO.read(inputStream);\n            inputStream.reset();\n\n            type = typeOf(image);\n            ByteArrayCopyStream imageStream = new ByteArrayCopyStream(image.getData().getDataBuffer().getSize());\n            ImageIO.write(image, type.getExtension(), imageStream);\n            stream = imageStream.toInput();\n\n            dimension = new Dimension(image.getWidth(), image.getHeight());\n        } else {\n            FileType fileType = FileTypeDetector.detectFileType(inputStream);\n            for (MetadataReader metadataReader : MetadataReaders.INSTANCES) {\n                if (metadataReader.canRead(fileType)) {\n                    try {\n                        // FIXME metadata-extractor 一直未发版支持 AVIF 格式，会被归为 QuickTime 格式\n                        if (fileType == FileType.QuickTime) {\n                            fileType = FileType.Heif;\n                        }\n                        Metadata metadata = ImageMetadataReader.readMetadata(inputStream, length, fileType);\n                        type = metadataReader.getType(metadata);\n                        dimension = metadataReader.getDimension(metadata);\n                        break;\n                    } catch (ImageProcessingException ignored) {\n                    }\n                }\n            }\n            inputStream.reset();\n            if (dimension == null) {\n                Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(inputStream);\n                while (imageReaders.hasNext()) {\n                    ImageReader reader = imageReaders.next();\n                    try {\n                        dimension = new Dimension(reader.getWidth(0), reader.getHeight(0));\n                        break;\n                    } catch (IOException ignored) {\n                    }\n                }\n                if (dimension == null) {\n                    BufferedImage image = ImageIO.read(inputStream);\n                    inputStream.reset();\n\n                    if (image == null) {\n                        return null;\n                    }\n\n                    if (type == null) {\n                        type = typeOf(image);\n                    }\n                    dimension = new Dimension(image.getWidth(), image.getHeight());\n                }\n            }\n        }\n        return new ImageInfo(stream, type, dimension);\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n\n    /**\n     * 添加图片\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @param inputStream 图片数据流\n     * @param type 图片类型\n     * @param widthInPixels 图片宽度（像素）\n     * @param heightInPixels 图片高度（像素）\n     * @param svgData SVG数据\n     */\n    protected void addPicture(Element element, HtmlRenderContext context, InputStream inputStream, int type,\n                              int widthInPixels, int heightInPixels,\n                              byte[] svgData) throws InvalidFormatException, IOException {\n        // 容器限制宽度\n        int containerWidth = context.getAvailableWidthInEMU();\n//        int containerHeight = context.getAvailablePageHeight();\n        // 图片原始宽高\n        int widthInEMU = Units.pixelToEMU(widthInPixels);\n        int heightInEMU = Units.pixelToEMU(heightInPixels);\n        float naturalAspect = 1f * widthInEMU / heightInEMU;\n\n        int declaredWidth = widthInEMU;\n        int declaredHeight = heightInEMU;\n        int maxWidthInEMU = containerWidth;\n        int maxHeightInEMU = Integer.MAX_VALUE;\n\n        String width = context.getPropertyValue(HtmlConstants.CSS_WIDTH);\n        if (width.length() > 0) {\n            CSSLength cssLength = CSSLength.of(width);\n            if (cssLength.isValid()) {\n                declaredWidth = context.computeLengthInEMU(cssLength, widthInEMU, containerWidth);\n            }\n        } else {\n            // width attribute is overridden by style, the same to height\n            // https://css-tricks.com/whats-the-difference-between-width-height-in-css-and-width-height-html-attributes/\n            width = element.attr(HtmlConstants.ATTR_WIDTH);\n            if (NumberUtils.isParsable(width)) {\n                width += HtmlConstants.PX;\n                CSSLength cssLength = CSSLength.of(width);\n                declaredWidth = context.computeLengthInEMU(cssLength, widthInEMU, containerWidth);\n            }\n        }\n\n\n        String maxWidth = context.getPropertyValue(HtmlConstants.CSS_MAX_WIDTH);\n        if (maxWidth.length() > 0) {\n            CSSLength cssLength = CSSLength.of(maxWidth);\n            if (cssLength.isValid()) {\n                // restrained by container\n                maxWidthInEMU = Math.min(context.computeLengthInEMU(cssLength, widthInEMU, containerWidth), containerWidth);\n            }\n        }\n\n        String height = context.getPropertyValue(HtmlConstants.CSS_HEIGHT);\n        if (height.length() > 0) {\n            CSSLength cssLength = CSSLength.of(height);\n            if (cssLength.isValid()) {\n                declaredHeight = context.computeLengthInEMU(cssLength, heightInEMU, Integer.MAX_VALUE);\n            }\n        } else {\n            height = element.attr(HtmlConstants.ATTR_HEIGHT);\n            if (NumberUtils.isParsable(height)) {\n                height += HtmlConstants.PX;\n                CSSLength cssLength = CSSLength.of(height);\n                declaredHeight = context.computeLengthInEMU(cssLength, heightInEMU, Integer.MAX_VALUE);\n            }\n        }\n\n        String maxHeight = context.getPropertyValue(HtmlConstants.CSS_MAX_HEIGHT);\n        if (maxHeight.length() > 0) {\n            CSSLength cssLength = CSSLength.of(maxHeight);\n            if (cssLength.isValid()) {\n                maxHeightInEMU = context.computeLengthInEMU(cssLength, heightInEMU, Integer.MAX_VALUE);\n            }\n        }\n\n        if (declaredWidth == widthInEMU ^ declaredHeight == heightInEMU) {\n            if (declaredWidth == widthInEMU) {\n                declaredWidth = (int) (declaredHeight * naturalAspect);\n            } else {\n                declaredHeight = (int) (declaredWidth / naturalAspect);\n            }\n        }\n\n        // 计算尺寸\n        int calculatedWidth, calculatedHeight;\n        if (declaredWidth < maxWidthInEMU && declaredHeight <= maxHeightInEMU) {\n            calculatedWidth = declaredWidth;\n            calculatedHeight = declaredHeight;\n        } else if (declaredWidth > maxWidthInEMU && declaredHeight <= maxHeightInEMU) {\n            calculatedWidth = maxWidthInEMU;\n            calculatedHeight = (int) (maxWidthInEMU / naturalAspect);\n        } else if (declaredHeight > maxHeightInEMU && declaredWidth <= maxWidthInEMU) {\n            calculatedHeight = maxHeightInEMU;\n            calculatedWidth = (int) (maxHeightInEMU * naturalAspect);\n        } else {\n            float widthRatio = 1f * maxWidthInEMU / declaredWidth;\n            float heightRatio = 1f * maxHeightInEMU / declaredHeight;\n            float scale = Math.min(widthRatio, heightRatio);\n            calculatedWidth = (int) (declaredWidth * scale);\n            calculatedHeight = (int) (declaredHeight * scale);\n        }\n\n        context.renderPicture(inputStream, type, HtmlConstants.TAG_IMG,\n            calculatedWidth, calculatedHeight, svgData);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/ItalicRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.CSSStyleUtils;\nimport org.jsoup.nodes.Element;\n\n/**\n * 斜体标签渲染器\n *\n * @author Draco\n * @since 2021-02-23\n */\npublic class ItalicRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_I, HtmlConstants.TAG_EM};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        context.pushInlineStyle(CSSStyleUtils.parse(HtmlConstants.DEFINED_ITALIC), element.isBlock());\n        return true;\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.popInlineStyle();\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/LaTeXRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.latex.LaTeXUtils;\nimport org.jsoup.nodes.Element;\nimport uk.ac.ed.ph.snuggletex.SnuggleSession;\n\n/**\n * latex标签渲染器（自定义）\n *\n * @author Draco\n * @since 2023-07-17\n */\npublic class LaTeXRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_LATEX};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        String latex = element.wholeText();\n        if (StringUtils.isBlank(latex)) {\n            return false;\n        }\n\n        SnuggleSession session = LaTeXUtils.createSession();\n        LaTeXUtils.parse(session, latex);\n        LaTeXUtils.renderTo(context.getClosestParagraph(), null, session, context.getMathRenderConfig());\n\n        return false;\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/ListItemRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.apache.poi.xwpf.usermodel.XWPFParagraph;\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.jsoup.nodes.Element;\n\n/**\n * 列表项渲染器\n *\n * @author Draco\n * @since 2021-02-19\n */\npublic class ListItemRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_LI};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        XWPFParagraph paragraph = context.getClosestParagraph();\n        context.markDedupe(paragraph);\n        context.getNumberingContext().add(paragraph);\n        return true;\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.unmarkDedupe();\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/ListRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.CSSLength;\nimport org.ddr.poi.html.util.ListStyle;\nimport org.ddr.poi.html.util.ListStyleType;\nimport org.ddr.poi.html.util.RenderUtils;\nimport org.jsoup.nodes.Element;\n\n/**\n * 列表渲染器\n *\n * @author Draco\n * @since 2021-02-18\n */\npublic class ListRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_UL, HtmlConstants.TAG_OL};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        String listStylePosition = context.currentElementStyle().getPropertyValue(HtmlConstants.CSS_LIST_STYLE_POSITION);\n        boolean hanging = !HtmlConstants.INSIDE.equals(listStylePosition);\n        CSSLength marginLeft = CSSLength.of(context.currentElementStyle().getMarginLeft().toLowerCase());\n        int left = marginLeft.isValid() && !marginLeft.isPercent()\n                ? RenderUtils.emuToTwips(context.lengthToEMU(marginLeft)) : 0;\n        CSSLength marginRight = CSSLength.of(context.currentElementStyle().getMarginRight().toLowerCase());\n        int right = marginRight.isValid() && !marginRight.isPercent()\n                ? RenderUtils.emuToTwips(context.lengthToEMU(marginRight)) : 0;\n        ListStyle listStyle = new ListStyle(determineNumberFormat(context, element), hanging, left, right);\n        context.getNumberingContext().startLevel(listStyle);\n        return true;\n    }\n\n    private ListStyleType determineNumberFormat(HtmlRenderContext context, Element element) {\n        String listStyleType = context.currentElementStyle()\n                .getPropertyValue(HtmlConstants.CSS_LIST_STYLE_TYPE).toLowerCase();\n        ListStyleType format;\n        switch (element.tag().normalName()) {\n            case HtmlConstants.TAG_OL:\n                if (StringUtils.isNotBlank(listStyleType)) {\n                    format = ListStyleType.Ordered.of(listStyleType);\n                } else {\n                    // 支持ol的type属性\n                    String type = element.attr(HtmlConstants.ATTR_TYPE);\n                    format = ListStyleType.Ordered.of(type);\n                }\n                break;\n            case HtmlConstants.TAG_UL:\n                format = ListStyleType.Unordered.of(listStyleType);\n                break;\n            default:\n                format = ListStyleType.Unordered.NONE;\n        }\n        return format;\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.getNumberingContext().endLevel();\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        // 列表标签本身不需要作为块状元素渲染，因为每一个列表项都是一个块状元素\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/MarkRenderer.java",
    "content": "/*\n * Copyright 2016 - 2022 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\nimport org.apache.commons.lang3.StringUtils;\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.jsoup.nodes.Element;\n\n/**\n * 标记标签渲染器\n *\n * @author Draco\n * @since 2022-06-11\n */\npublic class MarkRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_MARK};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        CSSStyleDeclarationImpl cssStyleDeclaration = context.currentElementStyle();\n\n        if (StringUtils.isEmpty(cssStyleDeclaration.getBackgroundColor())) {\n            cssStyleDeclaration.setBackgroundColor(\"yellow\");\n        }\n        return true;\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/MathRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.math.MathMLUtils;\nimport org.jsoup.nodes.Document.OutputSettings;\nimport org.jsoup.nodes.Document.OutputSettings.Syntax;\nimport org.jsoup.nodes.Element;\n\n/**\n * math标签渲染器\n *\n * @author Draco\n * @since 2021-02-18\n */\npublic class MathRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_MATH};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        OutputSettings outputSettings = element.ownerDocument().outputSettings();\n        outputSettings.syntax(Syntax.xml);\n        String math = element.outerHtml();\n        outputSettings.syntax(Syntax.html);\n        math = MathMLUtils.normalize(math);\n        MathMLUtils.renderTo(context.getClosestParagraph(), context.newRun(), math, context.getMathRenderConfig());\n\n        return false;\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/OmittedRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.jsoup.nodes.Element;\n\n/**\n * 直接忽略的元素渲染器\n *\n * @author Draco\n * @since 2021-03-15\n */\npublic class OmittedRenderer implements ElementRenderer {\n    private static final String[] TAGS = {\n            HtmlConstants.TAG_HEAD,\n            HtmlConstants.TAG_SCRIPT,\n            HtmlConstants.TAG_NOSCRIPT,\n            HtmlConstants.TAG_FRAME,\n            HtmlConstants.TAG_FRAMESET,\n            HtmlConstants.TAG_IFRAME,\n            HtmlConstants.TAG_NOFRAMES,\n            HtmlConstants.TAG_COLGROUP,\n            HtmlConstants.TAG_COL,\n            HtmlConstants.TAG_TEMPLATE,\n            HtmlConstants.TAG_RP,\n    };\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        return false;\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/PreRenderer.java",
    "content": "package org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.CSSStyleUtils;\nimport org.jsoup.nodes.Element;\n\n/**\n * pre标签渲染器\n *\n * @author Draco\n * @since 2023-06-25\n */\npublic class PreRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_PRE, HtmlConstants.TAG_XMP};\n\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        context.pushInlineStyle(CSSStyleUtils.parse(HtmlConstants.DEFINED_PRE), element.isBlock());\n        return true;\n    }\n\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.popInlineStyle();\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/RubyRenderer.java",
    "content": "/*\n * Copyright 2016 - 2022 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.xmlbeans.impl.xb.xmlschema.SpaceAttribute;\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.RenderUtils;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.TextNode;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRPr;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType;\n\n/**\n * ruby标签渲染器\n *\n * @author Draco\n * @since 2022-06-12 20:47\n */\npublic class RubyRenderer implements ElementRenderer {\n    private static final String[] TAGS = {\n            HtmlConstants.TAG_RUBY,\n    };\n\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        StringBuilder sb = StringUtil.borrowBuilder();\n        for (Node childNode : element.childNodes()) {\n            if (childNode instanceof Element) {\n                String tagName = ((Element) childNode).normalName();\n                if (HtmlConstants.TAG_RT.equals(tagName)) {\n                    String rt = ((Element) childNode).text();\n\n                    CTR ctr = context.newRun();\n                    CTRPr rPr = RenderUtils.getRPr(ctr);\n                    rPr.addNewLang().setVal(\"en-US\");\n                    ctr.addNewFldChar().setFldCharType(STFldCharType.BEGIN);\n\n                    ctr = context.newRun();\n                    rPr = RenderUtils.getRPr(ctr);\n                    rPr.addNewLang().setVal(\"en-US\");\n                    CTText ctText = ctr.addNewInstrText();\n                    ctText.setSpace(SpaceAttribute.Space.PRESERVE);\n\n                    int fontSize = context.getGlobalFontSize() == null\n                            ? context.getInheritedFontSizeInHalfPoints() : context.getGlobalFontSize().intValue();\n                    fontSize = (fontSize + 1) / 2;\n                    ctText.setStringValue(\"EQ \\\\* jc0 \\\\* hps\" + fontSize + \" \\\\o \\\\ad(\\\\s \\\\up \" + (fontSize - 1) + \"(\" + rt + \"),\"\n                            + StringUtil.releaseBuilder(sb).trim() + \")\");\n                    sb = StringUtil.borrowBuilder();\n\n                    ctr = context.newRun();\n                    rPr = RenderUtils.getRPr(ctr);\n                    rPr.addNewLang().setVal(\"en-US\");\n                    ctr.addNewFldChar().setFldCharType(STFldCharType.END);\n                } else if (HtmlConstants.TAG_RP.equals(tagName)) {\n                    continue;\n                } else {\n                    StringUtil.appendNormalisedWhitespace(sb, ((Element) childNode).wholeText(), false);\n                }\n            } else if (childNode instanceof TextNode) {\n                StringUtil.appendNormalisedWhitespace(sb, ((TextNode) childNode).getWholeText(), false);\n            }\n        }\n        String remainText = StringUtil.releaseBuilder(sb);\n        if (StringUtils.isNotBlank(remainText)) {\n            context.renderText(remainText);\n        }\n        return false;\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/SmallRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.CSSStyleUtils;\nimport org.jsoup.nodes.Element;\n\n/**\n * small标签渲染器\n *\n * @author Draco\n * @since 2021-02-23\n */\npublic class SmallRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_SMALL};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        context.pushInlineStyle(CSSStyleUtils.parse(HtmlConstants.DEFINED_SMALLER), element.isBlock());\n        return true;\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.popInlineStyle();\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/SubscriptRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.CSSStyleUtils;\nimport org.jsoup.nodes.Element;\n\n/**\n * sub标签渲染器\n *\n * @author Draco\n * @since 2021-02-24\n */\npublic class SubscriptRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_SUB};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        context.pushInlineStyle(CSSStyleUtils.parse(HtmlConstants.DEFINED_SUBSCRIPT), element.isBlock());\n        return true;\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.popInlineStyle();\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/SuperscriptRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.CSSStyleUtils;\nimport org.jsoup.nodes.Element;\n\n/**\n * sup标签渲染器\n *\n * @author Draco\n * @since 2021-02-24\n */\npublic class SuperscriptRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_SUP};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        context.pushInlineStyle(CSSStyleUtils.parse(HtmlConstants.DEFINED_SUPERSCRIPT), element.isBlock());\n        return true;\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.popInlineStyle();\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/SvgRenderer.java",
    "content": "package org.ddr.poi.html.tag;\n\nimport org.apache.poi.openxml4j.exceptions.InvalidFormatException;\nimport org.ddr.image.ImageType;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.util.ByteArrayCopyStream;\nimport org.jsoup.nodes.Element;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.imageio.ImageIO;\nimport java.awt.image.BufferedImage;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * svg标签渲染器\n *\n * @author Draco\n * @since 2022-04-13\n */\npublic class SvgRenderer extends ImageRenderer {\n    private static final Logger log = LoggerFactory.getLogger(SvgRenderer.class);\n\n    private static final String[] TAGS = {HtmlConstants.TAG_SVG};\n\n    static {\n        System.setProperty(\"com.twelvemonkeys.imageio.plugins.svg.allowExternalResources\", \"true\");\n    }\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        if (!element.hasAttr(\"xmlns\")) {\n            element.attr(\"xmlns\", \"http://www.w3.org/2000/svg\");\n        }\n        String svg = element.outerHtml().replace(\" />\", \"/>\");\n        byte[] bytes = svg.getBytes(StandardCharsets.UTF_8);\n        BufferedImage image;\n        try (InputStream svgStream = new ByteArrayInputStream(bytes)) {\n            image = ImageIO.read(svgStream);\n\n            ImageType type = typeOf(image);\n\n            int size = image.getData().getDataBuffer().getSize();\n            ByteArrayCopyStream outputStream = new ByteArrayCopyStream(size);\n            ImageIO.write(image, type.getExtension(), outputStream);\n\n            InputStream imageStream = outputStream.toInput();\n            addPicture(element, context, imageStream, type.getType(), image.getWidth(), image.getHeight(), bytes);\n        } catch (IOException | InvalidFormatException e) {\n            log.warn(\"Failed to render svg as image: {}\", svg, e);\n        } finally {\n            // 释放资源\n            image = null;\n        }\n        return false;\n    }\n\n    /**\n     * @return 支持的HTML标签\n     */\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/TableCellRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\nimport org.apache.commons.lang3.math.NumberUtils;\nimport org.apache.poi.xwpf.usermodel.XWPFParagraph;\nimport org.apache.poi.xwpf.usermodel.XWPFTable;\nimport org.apache.poi.xwpf.usermodel.XWPFTableCell;\nimport org.apache.xmlbeans.XmlCursor;\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.RenderUtils;\nimport org.ddr.poi.util.XmlUtils;\nimport org.jsoup.nodes.Element;\n\nimport java.util.List;\n\n/**\n * 表格单元格标签渲染器\n *\n * @author Draco\n * @since 2021-03-04\n */\npublic class TableCellRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_TH, HtmlConstants.TAG_TD};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        CSSStyleDeclarationImpl styleDeclaration = context.currentElementStyle();\n        int row = NumberUtils.toInt(element.attr(HtmlConstants.ATTR_ROW_INDEX));\n        int column = NumberUtils.toInt(element.attr(HtmlConstants.ATTR_COLUMN_INDEX));\n        XWPFTable table = context.getClosestTable();\n        XWPFTableCell cell = table.getRow(row).getCell(column);\n        context.pushContainer(cell);\n        XWPFParagraph paragraph = cell.getParagraphArray(0);\n        XmlCursor newCursor = paragraph.getCTP().newCursor();\n        // 指针指向单元格默认添加的段落，所有内容将被添加到该段落之前\n        context.pushCursor(newCursor);\n        newCursor.dispose();\n\n        RenderUtils.cellStyle(context, cell, styleDeclaration);\n\n        return true;\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        List<XWPFParagraph> paragraphs = context.getContainer().getParagraphs();\n        if (paragraphs.size() > 1) {\n            XmlCursor xmlCursor = context.currentCursorObject().newCursor();\n            if (xmlCursor.toPrevSibling() && XmlUtils.P_QNAME.equals(xmlCursor.getName())) {\n                ((XWPFTableCell) context.getContainer()).removeParagraph(paragraphs.size() - 1);\n            }\n            xmlCursor.dispose();\n        }\n\n        context.popContainer();\n        context.popCursor();\n        context.unmarkDedupe();\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        // 本身仅作为容器\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/TableRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.math.NumberUtils;\nimport org.apache.poi.util.Units;\nimport org.apache.poi.xwpf.usermodel.XWPFParagraph;\nimport org.apache.poi.xwpf.usermodel.XWPFTable;\nimport org.apache.poi.xwpf.usermodel.XWPFTableCell;\nimport org.apache.poi.xwpf.usermodel.XWPFTableRow;\nimport org.apache.xmlbeans.XmlCursor;\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.CSSLength;\nimport org.ddr.poi.html.util.CSSLengthUnit;\nimport org.ddr.poi.html.util.CSSStyleUtils;\nimport org.ddr.poi.html.util.ColumnStyle;\nimport org.ddr.poi.html.util.JsoupUtils;\nimport org.ddr.poi.html.util.RenderUtils;\nimport org.ddr.poi.html.util.Span;\nimport org.ddr.poi.html.util.SpanWidth;\nimport org.ddr.poi.html.util.WhiteSpaceRule;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.select.Elements;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBorder;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblGrid;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblGridCol;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth;\n\nimport java.math.BigInteger;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TreeMap;\n\n/**\n * 表格渲染器\n *\n * @author Draco\n * @since 2021-02-18\n */\npublic class TableRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_TABLE};\n\n    private final CSSStyleDeclarationImpl defaultCaptionStyle = new CSSStyleDeclarationImpl();\n\n    public TableRenderer() {\n        defaultCaptionStyle.setBackgroundColor(\"\");\n        defaultCaptionStyle.setBorder(\"\");\n        defaultCaptionStyle.setPadding(\"\");\n        defaultCaptionStyle.setMargin(\"\");\n        defaultCaptionStyle.setTextAlign(HtmlConstants.CENTER);\n\n        CSSStyleUtils.split(defaultCaptionStyle);\n    }\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        CSSStyleDeclarationImpl styleDeclaration = context.currentElementStyle();\n\n        WhiteSpaceRule tableWhiteSpace = WhiteSpaceRule.of(styleDeclaration.getWhiteSpace(), WhiteSpaceRule.NORMAL);\n        styleDeclaration.setWhiteSpace(HtmlConstants.NORMAL);\n        Map<Element, WhiteSpaceRule> whiteSpaceMap = new HashMap<>();\n        whiteSpaceMap.put(element, tableWhiteSpace);\n\n        String widthDeclaration = styleDeclaration.getWidth();\n\n        XWPFTable table = context.getClosestTable();\n        int containerWidth = context.getAvailableWidthInEMU();\n\n        CSSLength width = CSSLength.of(widthDeclaration);\n        boolean explicitWidth = width.isValid() && !width.isPercent();\n        int tableWidth = context.computeLengthInEMU(widthDeclaration, styleDeclaration.getMaxWidth(), containerWidth, containerWidth);\n        int originWidth = !width.isValid() || width.isPercent() ? tableWidth : context.lengthToEMU(width);\n\n        reorderTableChildren(element);\n\n        Element caption = JsoupUtils.firstChild(element, HtmlConstants.TAG_CAPTION);\n        if (caption != null) {\n            renderCaption(context, table, caption, tableWhiteSpace);\n        }\n\n        Element colgroup = JsoupUtils.firstChild(element, HtmlConstants.TAG_COLGROUP);\n        List<ColumnStyle> columnStyles = extractColumnStyles(colgroup);\n\n        CTTbl ctTbl = table.getCTTbl();\n        handleTableProperties(element, ctTbl);\n\n        boolean groupsRules = HtmlConstants.GROUPS.equals(element.attr(HtmlConstants.ATTR_RULES));\n        Elements trs = JsoupUtils.childRows(element);\n        Map<Integer, Span> rowSpanMap = new TreeMap<>();\n        TreeMap<Integer, CSSLength> colWidthMap = new TreeMap<>();\n        LinkedHashSet<SpanWidth> spanWidths = new LinkedHashSet<>();\n        Element lastTrParent = null;\n        for (int r = 0; r < trs.size(); r++) {\n            Element tr = trs.get(r);\n            Element trParent = tr.parent();\n            boolean renderGroupsGap = false;\n            if (lastTrParent != trParent) {\n                renderGroupsGap = groupsRules && lastTrParent!= null;\n                lastTrParent = trParent;\n            }\n            XWPFTableRow row = createRow(table, r);\n\n            Elements tds = JsoupUtils.children(tr, HtmlConstants.TAG_TH, HtmlConstants.TAG_TD);\n            int columnIndex = 0;\n            int minRowSpan = 1;\n            int vMergeCount = 0;\n            for (int c = 0; c < tds.size(); c++) {\n                Element td = tds.get(c);\n                int rowspan = NumberUtils.toInt(td.attr(HtmlConstants.ATTR_ROWSPAN), 1);\n                int colspan = NumberUtils.toInt(td.attr(HtmlConstants.ATTR_COLSPAN), 1);\n                minRowSpan = Math.min(minRowSpan, rowspan);\n\n                for (Map.Entry<Integer, Span> entry : rowSpanMap.entrySet()) {\n                    if (entry.getKey() <= columnIndex && entry.getValue().isEnabled()) {\n                        columnIndex += entry.getValue().getColumn();\n                        entry.getValue().setEnabled(false);\n                        // 合并行也需要生成单元格\n                        addVMergeCell(context, row, c, entry.getValue());\n                        vMergeCount++;\n                    }\n                }\n                // 标记行列索引，便于渲染单元格时获取容器\n                td.attr(HtmlConstants.ATTR_ROW_INDEX, String.valueOf(r));\n                td.attr(HtmlConstants.ATTR_COLUMN_INDEX, String.valueOf(c + vMergeCount));\n\n                WhiteSpaceRule tdWhiteSpace = null;\n                Element parent = td.parent();\n                while (true) {\n                    if (parent == element) {\n                        if (tdWhiteSpace == null) {\n                            tdWhiteSpace = tableWhiteSpace;\n                        }\n                        break;\n                    }\n                    WhiteSpaceRule parentWhiteSpace = whiteSpaceMap.get(parent);\n                    if (!whiteSpaceMap.containsKey(parent)) {\n                        CSSStyleDeclarationImpl parentStyle = context.getCssStyleDeclaration(parent);\n                        String parentRule = parentStyle.removeProperty(HtmlConstants.CSS_WHITE_SPACE);\n                        parentWhiteSpace = WhiteSpaceRule.of(parentRule);\n                        whiteSpaceMap.put(parent, parentWhiteSpace);\n                        if (parentWhiteSpace != null) {\n                            parent.attr(HtmlConstants.ATTR_STYLE, parentStyle.getCssText());\n                        }\n                    }\n                    if (parentWhiteSpace != null && tdWhiteSpace == null) {\n                        tdWhiteSpace = parentWhiteSpace;\n                    }\n                    parent = parent.parent();\n                }\n\n                if (!tdWhiteSpace.isNormal()) {\n                    td.attr(HtmlConstants.ATTR_STYLE, HtmlConstants.CSS_WHITE_SPACE\n                            + HtmlConstants.COLON + tdWhiteSpace.getValue() + HtmlConstants.SEMICOLON\n                            + td.attr(HtmlConstants.ATTR_STYLE));\n                }\n\n                // 列定义的样式与单元格的样式合并\n                if (!columnStyles.isEmpty() && columnIndex < columnStyles.size()) {\n                    String colStyle = columnStyles.get(columnIndex).getStyle().getCssText();\n                    StringBuilder sb = new StringBuilder();\n                    if (!colStyle.isEmpty()) {\n                        sb.append(colStyle).append(HtmlConstants.SEMICOLON);\n                    }\n                    if (colspan > 1) {\n                        CSSLength tdWidth = sumColumnWidths(columnStyles, columnIndex, colspan);\n                        if (tdWidth.isValid()) {\n                            sb.append(HtmlConstants.CSS_WIDTH).append(HtmlConstants.COLON)\n                                    .append(tdWidth).append(HtmlConstants.SEMICOLON);\n                        }\n                    }\n                    if (sb.length() > 0) {\n                        sb.append(td.attr(HtmlConstants.ATTR_STYLE));\n                        td.attr(HtmlConstants.ATTR_STYLE, sb.toString());\n                    }\n                }\n\n                CSSStyleDeclarationImpl tdStyleDeclaration = CSSStyleUtils.parse(td.attr(HtmlConstants.ATTR_STYLE));\n                CSSLength tdWidth = CSSLength.of(tdStyleDeclaration.getWidth());\n\n                // 必须晚于之前列的行合并单元格创建\n                XWPFTableCell cell = createCell(row, c);\n                CTTcPr ctTcPr = RenderUtils.getTcPr(cell.getCTTc());\n                if (rowspan > 1) {\n                    CSSStyleUtils.split(tdStyleDeclaration);\n                    rowSpanMap.put(columnIndex, new Span(rowspan, colspan, false, tdStyleDeclaration));\n                    CTVMerge ctvMerge = ctTcPr.isSetVMerge() ? ctTcPr.getVMerge() : ctTcPr.addNewVMerge();\n                    ctvMerge.setVal(STMerge.RESTART);\n                }\n                if (colspan == 1) {\n                    CSSLength existingWidth = colWidthMap.get(columnIndex);\n                    if (existingWidth == null || !existingWidth.isValid()) {\n                        colWidthMap.put(columnIndex, tdWidth);\n                    } else {\n                        // 根据表格本身是否使用百分比的宽度定义，来确定单元格宽度的定义方式\n                        if (explicitWidth) {\n                            if (existingWidth.isPercent()) {\n                                colWidthMap.put(columnIndex, tdWidth);\n                            }\n                        } else {\n                            if (!existingWidth.isPercent()) {\n                                colWidthMap.put(columnIndex, tdWidth);\n                            }\n                        }\n                    }\n                } else {\n                    spanWidths.add(new SpanWidth(tdWidth, columnIndex, colspan, explicitWidth));\n                    ctTcPr.addNewGridSpan().setVal(BigInteger.valueOf(colspan));\n                }\n\n                if (renderGroupsGap) {\n                    RenderUtils.getTableCellTop(cell.getCTTc()).setVal(STBorder.SINGLE);\n                }\n\n                columnIndex += colspan;\n            }\n\n            for (Iterator<Map.Entry<Integer, Span>> iterator = rowSpanMap.entrySet().iterator(); iterator.hasNext(); ) {\n                Map.Entry<Integer, Span> entry = iterator.next();\n                Integer spanColumnIndex = entry.getKey();\n                Span span = entry.getValue();\n                span.setRow(span.getRow() - minRowSpan);\n                if (span.getRow() == 0) {\n                    iterator.remove();\n                } else {\n                    span.setEnabled(true);\n                }\n                if (columnIndex <= spanColumnIndex && columnIndex < colWidthMap.size()) {\n                    addVMergeCell(context, row, columnIndex, span);\n                    columnIndex += span.getColumn();\n                }\n            }\n        }\n\n        CTTblGrid tblGrid = ctTbl.getTblGrid();\n        if (tblGrid == null) {\n            tblGrid = ctTbl.addNewTblGrid();\n        }\n        for (SpanWidth spanWidth : spanWidths) {\n            spanWidth.setLength(colWidthMap);\n        }\n\n        BigInteger[] colWidths = new BigInteger[colWidthMap.size()];\n        // 未处理的百分比总和\n        double unhandledPercentSum = 0;\n        // 未处理的emu长度总和\n        int unhandledEmuSum = 0;\n        // 未处理的emu长度数量\n        int unhandledEmuCount = 0;\n        // 可用的宽度\n        int remainWidth = tableWidth;\n        for (Iterator<Map.Entry<Integer, CSSLength>> iterator = colWidthMap.entrySet().iterator(); iterator.hasNext(); ) {\n            Map.Entry<Integer, CSSLength> entry = iterator.next();\n            CSSLength value = entry.getValue();\n            if (!value.isValid()) {\n                entry.setValue(new CSSLength(100d / colWidths.length, CSSLengthUnit.PERCENT));\n                unhandledPercentSum += entry.getValue().getValue();\n                continue;\n            }\n\n            if (explicitWidth) {\n                if (value.isPercent()) {\n                    unhandledPercentSum += value.getValue();\n                } else {\n                    int emu = (int) ((long) value.toEMU() * tableWidth / originWidth);\n                    colWidths[entry.getKey()] = BigInteger.valueOf(RenderUtils.emuToTwips(emu));\n                    remainWidth -= emu;\n                    iterator.remove();\n                }\n            } else {\n                if (value.isPercent()) {\n                    unhandledPercentSum += value.getValue();\n                } else {\n                    unhandledPercentSum += 100d / colWidths.length;\n                    unhandledEmuSum += value.toEMU();\n                    unhandledEmuCount++;\n                }\n            }\n        }\n        // 处理剩余未处理的列宽\n        for (Map.Entry<Integer, CSSLength> entry : colWidthMap.entrySet()) {\n            CSSLength value = entry.getValue();\n            if (value.isPercent()) {\n                colWidths[entry.getKey()] = BigInteger.valueOf((int) Math.rint(remainWidth * value.getValue() / unhandledPercentSum * 20 / Units.EMU_PER_POINT));\n            } else {\n                colWidths[entry.getKey()] = BigInteger.valueOf((int) Math.rint(remainWidth * (unhandledEmuCount * 100d / colWidths.length / unhandledPercentSum) * value.toEMU() / unhandledEmuSum * 20 / Units.EMU_PER_POINT));\n            }\n        }\n        for (BigInteger colWidth : colWidths) {\n            CTTblGridCol ctTblGridCol = tblGrid.addNewGridCol();\n            ctTblGridCol.setW(colWidth);\n        }\n        for (int i = 0, rows = ctTbl.sizeOfTrArray(); i < rows; i++) {\n            CTRow ctRow = ctTbl.getTrArray(i);\n            int columnIndex = 0;\n            for (int j = 0, cells = ctRow.sizeOfTcArray(); j < cells; j++) {\n                CTTc ctTc = ctRow.getTcArray(j);\n                CTTcPr tcPr = RenderUtils.getTcPr(ctTc);\n                int colspan = tcPr.isSetGridSpan() ? tcPr.getGridSpan().getVal().intValue() : 1;\n                CTTblWidth tcWidth = tcPr.addNewTcW();\n                tcWidth.setType(STTblWidth.DXA);\n                if (colspan == 1) {\n                    tcWidth.setW(colWidths[columnIndex]);\n                } else {\n                    int sum = 0;\n                    for (int k = 0; k < colspan; k++) {\n                        sum += colWidths[columnIndex + k].intValue();\n                    }\n                    tcWidth.setW(BigInteger.valueOf(sum));\n                }\n                columnIndex += colspan;\n            }\n        }\n        for (Element tr : trs) {\n            for (Element td : tr.children()) {\n                if (!td.hasAttr(HtmlConstants.ATTR_COLUMN_INDEX)) {\n                    continue;\n                }\n                int colspan = NumberUtils.toInt(td.attr(HtmlConstants.ATTR_COLSPAN), 1);\n                int columnIndex = Integer.parseInt(td.attr(HtmlConstants.ATTR_COLUMN_INDEX));\n                int sum = 0;\n                if (colspan == 1) {\n                    sum = colWidths[columnIndex].intValue();\n                } else {\n                    for (int k = 0; k < colspan; k++) {\n                        sum += colWidths[columnIndex + k].intValue();\n                    }\n                }\n                String tdWidth = sum + CSSLengthUnit.TWIP.getLiteral();\n                String style = td.attr(HtmlConstants.ATTR_STYLE).trim();\n                if (style.isEmpty()) {\n                    style = HtmlConstants.ATTR_WIDTH + HtmlConstants.COLON + tdWidth;\n                } else {\n                    style += (style.endsWith(HtmlConstants.SEMICOLON) ? \"\" : HtmlConstants.SEMICOLON) + HtmlConstants.ATTR_WIDTH + HtmlConstants.COLON + tdWidth;\n                }\n\n                td.attr(HtmlConstants.ATTR_STYLE, style);\n            }\n        }\n\n        return true;\n    }\n\n    private void reorderTableChildren(Element element) {\n        Elements theads = JsoupUtils.children(element, HtmlConstants.TAG_THEAD);\n        for (int i = theads.size() - 1; i >= 0; i--) {\n            Element thead = theads.get(i);\n            thead.remove();\n            element.prependChild(thead);\n        }\n        Elements tfoots = JsoupUtils.children(element, HtmlConstants.TAG_TFOOT);\n        for (Element tfoot : tfoots) {\n            tfoot.remove();\n            element.appendChild(tfoot);\n        }\n    }\n\n    /**\n     * <a href=\"https://www.w3.org/TR/html401/struct/tables.html#adef-border-TABLE\">link</a>\n     */\n    private void handleTableProperties(Element element, CTTbl ctTbl) {\n        String frameValue = null;\n        String rulesValue = null;\n        int borderValue = 0;\n        if (element.hasAttr(HtmlConstants.BORDER)) {\n            String border = element.attr(HtmlConstants.BORDER);\n            frameValue = HtmlConstants.VOID;\n            rulesValue = HtmlConstants.NONE;\n            if (!\"0\".equals(border)) {\n                if (StringUtils.isNumeric(border) && !border.startsWith(\"0\") && (borderValue = Integer.parseInt(border)) > 0) {\n                    frameValue = \"\";\n                } else {\n                    frameValue = HtmlConstants.BORDER;\n                }\n                rulesValue = HtmlConstants.ALL;\n            }\n        }\n        if (element.hasAttr(HtmlConstants.ATTR_FRAME)) {\n            frameValue = element.attr(HtmlConstants.ATTR_FRAME);\n        }\n        if (element.hasAttr(HtmlConstants.ATTR_RULES)) {\n            rulesValue = element.attr(HtmlConstants.ATTR_RULES);\n        }\n\n        if (frameValue != null) {\n            CTBorder tableTop = RenderUtils.getTableTop(ctTbl);\n            CTBorder tableBottom = RenderUtils.getTableBottom(ctTbl);\n            CTBorder tableLeft = RenderUtils.getTableLeft(ctTbl);\n            CTBorder tableRight = RenderUtils.getTableRight(ctTbl);\n            switch (frameValue) {\n                case HtmlConstants.VOID:\n                    tableTop.setVal(STBorder.NONE);\n                    tableBottom.setVal(STBorder.NONE);\n                    tableLeft.setVal(STBorder.NONE);\n                    tableRight.setVal(STBorder.NONE);\n                    break;\n                case HtmlConstants.ABOVE:\n                    tableTop.setVal(STBorder.SINGLE);\n                    tableBottom.setVal(STBorder.NONE);\n                    tableLeft.setVal(STBorder.NONE);\n                    tableRight.setVal(STBorder.NONE);\n                    break;\n                case HtmlConstants.BELOW:\n                    tableTop.setVal(STBorder.NONE);\n                    tableBottom.setVal(STBorder.SINGLE);\n                    tableLeft.setVal(STBorder.NONE);\n                    tableRight.setVal(STBorder.NONE);\n                    break;\n                case HtmlConstants.LHS:\n                    tableTop.setVal(STBorder.NONE);\n                    tableBottom.setVal(STBorder.NONE);\n                    tableLeft.setVal(STBorder.SINGLE);\n                    tableRight.setVal(STBorder.NONE);\n                    break;\n                case HtmlConstants.RHS:\n                    tableTop.setVal(STBorder.NONE);\n                    tableBottom.setVal(STBorder.NONE);\n                    tableLeft.setVal(STBorder.NONE);\n                    tableRight.setVal(STBorder.SINGLE);\n                    break;\n                case HtmlConstants.H_SIDES:\n                    tableTop.setVal(STBorder.SINGLE);\n                    tableBottom.setVal(STBorder.SINGLE);\n                    tableLeft.setVal(STBorder.NONE);\n                    tableRight.setVal(STBorder.NONE);\n                    break;\n                case HtmlConstants.V_SIDES:\n                    tableTop.setVal(STBorder.NONE);\n                    tableBottom.setVal(STBorder.NONE);\n                    tableLeft.setVal(STBorder.SINGLE);\n                    tableRight.setVal(STBorder.SINGLE);\n                    break;\n                case HtmlConstants.BOX:\n                case HtmlConstants.BORDER:\n                    tableTop.setVal(STBorder.SINGLE);\n                    tableBottom.setVal(STBorder.SINGLE);\n                    tableLeft.setVal(STBorder.SINGLE);\n                    tableRight.setVal(STBorder.SINGLE);\n                    break;\n                default:\n                    tableTop.setVal(STBorder.SINGLE);\n                    tableBottom.setVal(STBorder.SINGLE);\n                    tableLeft.setVal(STBorder.SINGLE);\n                    tableRight.setVal(STBorder.SINGLE);\n                    if (borderValue > 0) {\n                        long thickness = (long) RenderUtils.BORDER_WIDTH_PER_PX * borderValue;\n                        thickness = Math.min(thickness, RenderUtils.MAX_BORDER_WIDTH);\n                        BigInteger sz = BigInteger.valueOf(thickness);\n                        tableTop.setSz(sz);\n                        tableBottom.setSz(sz);\n                        tableLeft.setSz(sz);\n                        tableRight.setSz(sz);\n                    }\n                    break;\n            }\n        }\n\n        if (rulesValue != null) {\n            switch (rulesValue) {\n                case HtmlConstants.NONE:\n                case HtmlConstants.GROUPS:\n                    RenderUtils.getTblInsideH(RenderUtils.getTblBorders(ctTbl)).setVal(STBorder.NONE);\n                    RenderUtils.getTblInsideV(RenderUtils.getTblBorders(ctTbl)).setVal(STBorder.NONE);\n                    break;\n                case HtmlConstants.ROWS:\n                    RenderUtils.getTblInsideH(RenderUtils.getTblBorders(ctTbl)).setVal(STBorder.SINGLE);\n                    RenderUtils.getTblInsideV(RenderUtils.getTblBorders(ctTbl)).setVal(STBorder.NONE);\n                    break;\n                case HtmlConstants.COLS:\n                    RenderUtils.getTblInsideH(RenderUtils.getTblBorders(ctTbl)).setVal(STBorder.NONE);\n                    RenderUtils.getTblInsideV(RenderUtils.getTblBorders(ctTbl)).setVal(STBorder.SINGLE);\n                    break;\n                case HtmlConstants.ALL:\n                default:\n                    RenderUtils.getTblInsideH(RenderUtils.getTblBorders(ctTbl)).setVal(STBorder.SINGLE);\n                    RenderUtils.getTblInsideV(RenderUtils.getTblBorders(ctTbl)).setVal(STBorder.SINGLE);\n                    break;\n            }\n        }\n    }\n\n    private List<ColumnStyle> extractColumnStyles(Element colgroup) {\n        List<ColumnStyle> columnStyles = Collections.emptyList();\n        if (colgroup != null) {\n            Elements cols = colgroup.select(HtmlConstants.TAG_COL);\n            columnStyles = new ArrayList<>();\n            for (Element col : cols) {\n                String style = col.attr(HtmlConstants.ATTR_STYLE);\n                CSSStyleDeclarationImpl cssStyleDeclaration = CSSStyleUtils.parse(style);\n                int span = NumberUtils.toInt(col.attr(HtmlConstants.ATTR_SPAN), 1);\n                // 宽度样式优先于宽度属性\n                CSSLength colWidth = CSSLength.of(cssStyleDeclaration.getWidth());\n                if (!colWidth.isValid()) {\n                    String colWidthAttr = col.attr(HtmlConstants.ATTR_WIDTH);\n                    if (!colWidthAttr.isEmpty()) {\n                        if (!colWidthAttr.endsWith(HtmlConstants.PERCENT)) {\n                            colWidthAttr += HtmlConstants.PX;\n                        }\n                        colWidth = CSSLength.of(colWidthAttr);\n                    }\n                }\n                if (colWidth.isValid() && span > 1) {\n                    colWidth = new CSSLength(colWidth.getValue() / span, colWidth.getUnit());\n                }\n                for (int i = 0; i < span; i++) {\n                    columnStyles.add(new ColumnStyle(cssStyleDeclaration, colWidth));\n                }\n            }\n            colgroup.remove();\n        }\n        return columnStyles;\n    }\n\n    private CSSLength sumColumnWidths(List<ColumnStyle> columnWidths, int columnIndex, int colspan) {\n        Boolean percent = null;\n        double sum = 0d;\n        int availableSpan = Math.min(columnWidths.size() - columnIndex, colspan);\n        for (int i = 0; i < availableSpan; i++) {\n            CSSLength width = columnWidths.get(columnIndex + i).getWidth();\n            if (!width.isValid()) {\n                return width;\n            }\n            if (percent == null) {\n                percent = width.isPercent();\n            } else if (percent ^ width.isPercent()) {\n                return CSSLength.INVALID;\n            }\n            if (percent) {\n                sum += width.getValue();\n            } else {\n                sum += width.toEMU();\n            }\n        }\n        return new CSSLength(sum, percent ? CSSLengthUnit.PERCENT : CSSLengthUnit.EMU);\n    }\n\n    /**\n     * 渲染标题\n     *\n     * @param context 渲染上下文\n     * @param table 表格\n     * @param caption 标题元素\n     * @param whiteSpace table标签上的white-space样式\n     */\n    private void renderCaption(HtmlRenderContext context, XWPFTable table, Element caption, WhiteSpaceRule whiteSpace) {\n        CSSStyleDeclarationImpl captionStyle = context.getCssStyleDeclaration(caption);\n        captionStyle.getProperties().addAll(0, defaultCaptionStyle.getProperties());\n        if (whiteSpace != null) {\n            captionStyle.getProperties().add(0, CSSStyleUtils.newProperty(HtmlConstants.CSS_WHITE_SPACE, whiteSpace.getValue()));\n        }\n        context.pushInlineStyle(captionStyle, caption.isBlock());\n        XWPFParagraph captionParagraph;\n        boolean bottom = HtmlConstants.BOTTOM.equals(context.getPropertyValue(HtmlConstants.CSS_CAPTION_SIDE));\n        if (bottom) {\n            captionParagraph = context.getClosestParagraph();\n            caption.parent().attr(HtmlConstants.CSS_CAPTION_SIDE, HtmlConstants.BOTTOM);\n        } else {\n            // 表格上方添加标题\n            XmlCursor xmlCursor = table.getCTTbl().newCursor();\n            context.pushCursor(xmlCursor);\n            captionParagraph = context.newParagraph(null, xmlCursor);\n            xmlCursor.dispose();\n        }\n        RenderUtils.paragraphStyle(context, captionParagraph, captionStyle);\n\n        context.markDedupe(captionParagraph);\n        for (Node node : caption.childNodes()) {\n            context.renderNode(node);\n        }\n        context.unmarkDedupe();\n\n        context.popInlineStyle();\n        caption.remove();\n        if (bottom) {\n            XmlCursor xmlCursor = captionParagraph.getCTP().newCursor();\n            context.pushCursor(xmlCursor);\n            xmlCursor.dispose();\n        } else {\n            context.popCursor();\n        }\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        if (HtmlConstants.BOTTOM.equals(element.attr(HtmlConstants.CSS_CAPTION_SIDE))) {\n            context.popCursor();\n        }\n    }\n\n    /**\n     * 添加垂直合并的单元格\n     *\n     * @param row 行\n     * @param columnIndex 列索引\n     * @param span 跨行列参数\n     */\n    private void addVMergeCell(HtmlRenderContext context, XWPFTableRow row, int columnIndex, Span span) {\n        XWPFTableCell cell = createCell(row, columnIndex);\n        CTTcPr ctTcPr = RenderUtils.getTcPr(cell.getCTTc());\n        ctTcPr.addNewVMerge();\n        RenderUtils.setBorder(context, cell, span.getStyle());\n        if (span.getColumn() > 1) {\n            ctTcPr.addNewGridSpan().setVal(BigInteger.valueOf(span.getColumn()));\n        }\n    }\n\n    /**\n     * 创建单元格\n     *\n     * @param row 行\n     * @param c 列索引\n     * @return 单元格\n     */\n    private XWPFTableCell createCell(XWPFTableRow row, int c) {\n        return row.createCell();\n    }\n\n    /**\n     * 创建行\n     *\n     * @param table 表格\n     * @param r 行索引\n     * @return 行\n     */\n    private XWPFTableRow createRow(XWPFTable table, int r) {\n        // 避免使用createRow，因为不需要自动创建单元格\n        return table.insertNewTableRow(r);\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/UnderlineRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.ddr.poi.html.util.CSSStyleUtils;\nimport org.jsoup.nodes.Element;\n\n/**\n * u标签渲染器\n *\n * @author Draco\n * @since 2021-02-23\n */\npublic class UnderlineRenderer implements ElementRenderer {\n    private static final String[] TAGS = {HtmlConstants.TAG_U};\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        context.pushInlineStyle(CSSStyleUtils.parse(HtmlConstants.DEFINED_UNDERLINE), element.isBlock());\n        return true;\n    }\n\n    /**\n     * 元素渲染结束需要执行的逻辑\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     */\n    @Override\n    public void renderEnd(Element element, HtmlRenderContext context) {\n        context.popInlineStyle();\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/WalkThroughRenderer.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.jsoup.nodes.Element;\n\n/**\n * 穿透的元素，即标签仅作为样式的载体，但是不渲染为内容容器\n *\n * @author Draco\n * @since 2021-03-15\n */\npublic class WalkThroughRenderer implements ElementRenderer {\n    private static final String[] TAGS = {\n            HtmlConstants.TAG_HTML,\n            HtmlConstants.TAG_BODY,\n            HtmlConstants.TAG_THEAD,\n            HtmlConstants.TAG_TBODY,\n            HtmlConstants.TAG_TR,\n            HtmlConstants.TAG_TFOOT\n    };\n\n    /**\n     * 开始渲染\n     *\n     * @param element HTML元素\n     * @param context 渲染上下文\n     * @return 是否继续渲染子元素\n     */\n    @Override\n    public boolean renderStart(Element element, HtmlRenderContext context) {\n        return true;\n    }\n\n    @Override\n    public String[] supportedTags() {\n        return TAGS;\n    }\n\n    @Override\n    public boolean renderAsBlock() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/BoxProperty.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.util;\n\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\nimport com.steadystate.css.dom.Property;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.w3c.dom.css.CSSValue;\n\n/**\n * CSS中支持四个边的属性\n *\n * @author Draco\n * @since 2021-03-29\n */\npublic enum BoxProperty {\n    MARGIN(HtmlConstants.CSS_MARGIN_TOP, HtmlConstants.CSS_MARGIN_RIGHT,\n            HtmlConstants.CSS_MARGIN_BOTTOM, HtmlConstants.CSS_MARGIN_LEFT),\n    PADDING(HtmlConstants.CSS_PADDING_TOP, HtmlConstants.CSS_PADDING_RIGHT,\n            HtmlConstants.CSS_PADDING_BOTTOM, HtmlConstants.CSS_PADDING_LEFT),\n    BORDER_STYLE(HtmlConstants.CSS_BORDER_TOP_STYLE, HtmlConstants.CSS_BORDER_RIGHT_STYLE,\n            HtmlConstants.CSS_BORDER_BOTTOM_STYLE, HtmlConstants.CSS_BORDER_LEFT_STYLE),\n    BORDER_WIDTH(HtmlConstants.CSS_BORDER_TOP_WIDTH, HtmlConstants.CSS_BORDER_RIGHT_WIDTH,\n            HtmlConstants.CSS_BORDER_BOTTOM_WIDTH, HtmlConstants.CSS_BORDER_LEFT_WIDTH),\n    BORDER_COLOR(HtmlConstants.CSS_BORDER_TOP_COLOR, HtmlConstants.CSS_BORDER_RIGHT_COLOR,\n            HtmlConstants.CSS_BORDER_BOTTOM_COLOR, HtmlConstants.CSS_BORDER_LEFT_COLOR);\n\n    private final String top;\n    private final String right;\n    private final String bottom;\n    private final String left;\n\n    BoxProperty(String top, String right, String bottom, String left) {\n        this.top = top;\n        this.right = right;\n        this.bottom = bottom;\n        this.left = left;\n    }\n\n    public void setValues(CSSStyleDeclarationImpl cssStyleDeclaration, int i,\n                          CSSValue topValue, CSSValue rightValue, CSSValue bottomValue, CSSValue leftValue) {\n        cssStyleDeclaration.getProperties().add(i, new Property(top, topValue, false));\n        cssStyleDeclaration.getProperties().add(i, new Property(right, rightValue, false));\n        cssStyleDeclaration.getProperties().add(i, new Property(bottom, bottomValue, false));\n        cssStyleDeclaration.getProperties().add(i, new Property(left, leftValue, false));\n    }\n\n    public void setValues(CSSStyleDeclarationImpl cssStyleDeclaration, int i, CSSValue value) {\n        setValues(cssStyleDeclaration, i, value, value, value, value);\n    }\n\n    public void setValues(CSSStyleDeclarationImpl cssStyleDeclaration, int i, CSSValue topBottom, CSSValue rightLeft) {\n        setValues(cssStyleDeclaration, i, topBottom, rightLeft, topBottom, rightLeft);\n    }\n\n    public void setValues(CSSStyleDeclarationImpl cssStyleDeclaration, int i, CSSValue top, CSSValue rightLeft, CSSValue bottom) {\n        setValues(cssStyleDeclaration, i, top, rightLeft, bottom, rightLeft);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/CSSLength.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.util;\n\nimport java.util.Arrays;\nimport java.util.Objects;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\n\n/**\n * CSS长度值\n *\n * @author Draco\n * @since 2021-03-01\n */\npublic class CSSLength {\n    private static final Pattern LENGTH_PATTERN = Pattern.compile(\n            \"(-?\\\\d+\\\\.?\\\\d*)(\" +\n                    Arrays.stream(CSSLengthUnit.values()).filter(CSSLengthUnit::isSystem)\n                            .map(CSSLengthUnit::getLiteral).collect(Collectors.joining(\"|\"))\n                    + \")\"\n    );\n\n    public static final CSSLength INVALID = new CSSLength(Double.NaN, null);\n\n    private double value;\n    private CSSLengthUnit unit;\n\n    public CSSLength(double value, CSSLengthUnit unit) {\n        this.value = value;\n        this.unit = unit;\n    }\n\n    public double getValue() {\n        return value;\n    }\n\n    public CSSLengthUnit getUnit() {\n        return unit;\n    }\n\n    @Override\n    public String toString() {\n        return String.format(\"%.2f\", value) + (unit == null ? \"\" : unit.getLiteral());\n    }\n\n    /**\n     * 单位转EMU，适用于绝对长度单位\n     */\n    public int toEMU() {\n        validate();\n        requireAbsoluteUnit();\n        return (int) Math.rint(unitValue());\n    }\n\n    public double unitValue() {\n        return value * unit.absoluteFactor();\n    }\n\n    private void requireAbsoluteUnit() {\n        if (unit.isRelative()) {\n            throw new UnsupportedOperationException(\"Can not convert a relative length to EMU: \" + toString());\n        }\n    }\n\n    private void validate() {\n        if (!isValid()) {\n            throw new UnsupportedOperationException(\"Invalid CSS length\");\n        }\n    }\n\n    public CSSLength to(CSSLengthUnit other) {\n        validate();\n        return new CSSLength(value * unit.to(other), other);\n    }\n\n    public int toHalfPoints() {\n        validate();\n        return (int) Math.rint(value * unit.to(CSSLengthUnit.PT) * 2);\n    }\n\n    public boolean isValid() {\n        return unit != null && !Double.isNaN(value) && !Double.isInfinite(value);\n    }\n\n    public boolean isPercent() {\n        return unit == CSSLengthUnit.PERCENT;\n    }\n\n    public boolean isValidPercent() {\n        return isValid() && isPercent();\n    }\n\n    public static CSSLength of(String text) {\n        if (text == null || text.isEmpty()) {\n            return INVALID;\n        }\n        Matcher matcher = LENGTH_PATTERN.matcher(text);\n        if (matcher.matches()) {\n            return new CSSLength(Double.parseDouble(matcher.group(1)), CSSLengthUnit.of(matcher.group(2)));\n        }\n        return INVALID;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        CSSLength other = (CSSLength) o;\n        if (!isValid() && !other.isValid()) return true;\n        return Double.compare(other.value, value) == 0 &&\n                unit == other.unit;\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(value, unit);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/CSSLengthUnit.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.util;\n\nimport org.apache.poi.util.Units;\nimport org.ddr.poi.html.HtmlConstants;\n\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * 长度单位\n *\n * @author Draco\n * @since 2021-03-01\n */\npublic enum CSSLengthUnit {\n    CM(HtmlConstants.CM, true, false, false, Units.EMU_PER_CENTIMETER, 1, 1),\n    MM(HtmlConstants.MM, true, false, false, Units.EMU_PER_CENTIMETER, 10, -1),\n    IN(HtmlConstants.IN, true, false, false, Units.EMU_PER_POINT, 72, 1),\n    PX(HtmlConstants.PX, true, false, false, Units.EMU_PER_PIXEL, 1, 1),\n    PT(HtmlConstants.PT, true, false, false, Units.EMU_PER_POINT, 1, 1),\n    PC(HtmlConstants.PC, true, false, false, Units.EMU_PER_POINT, 12, 1),\n\n    EMU(HtmlConstants.EMU, false, false, false, 1, 1, 1),\n    TWIP(HtmlConstants.TWIP, false, false, false, Units.EMU_PER_POINT, 20, -1),\n\n    REM(HtmlConstants.REM, true, true, false, 1, 1, 1),\n    EM(HtmlConstants.EM, true, true, true, 1, 1, 1),\n    VW(HtmlConstants.VW, true, true, false, 1, 100, -1),\n    VH(HtmlConstants.VH, true, true, false, 1, 100, -1),\n    VMIN(HtmlConstants.VMIN, true, true, false, 1, 100, -1),\n    VMAX(HtmlConstants.VMAX, true, true, false, 1, 100, -1),\n\n    PERCENT(HtmlConstants.PERCENT, true, true, true, 1, 100, -1);\n\n    private static final Map<String, CSSLengthUnit> LITERAL_MAP = Arrays.stream(values())\n            .filter(CSSLengthUnit::isSystem)\n            .collect(Collectors.toMap(CSSLengthUnit::getLiteral, Function.identity()));\n\n    /**\n     * 单位字面值\n     */\n    private final String literal;\n    /**\n     * 是否为系统单位，该枚举中包含部分自定义单位以便换算\n     */\n    private final boolean system;\n    /**\n     * 是否为相对长度\n     */\n    private final boolean relative;\n    /**\n     * 是否相对父元素，false表示相对于根元素\n     */\n    private final boolean relativeToParent;\n    // 下面3个属性联合表示单位系数，绝对长度以EMU为基准，相对长度以1为基准\n    private final int unit;\n    private final int factor;\n    private final int power;\n\n    CSSLengthUnit(String literal, boolean system, boolean relative, boolean relativeToParent, int unit, int factor, int power) {\n        this.literal = literal;\n        this.system = system;\n        this.relative = relative;\n        this.relativeToParent = relativeToParent;\n        this.unit = unit;\n        this.factor = factor;\n        this.power = power;\n    }\n\n    public String getLiteral() {\n        return literal;\n    }\n\n    public boolean isSystem() {\n        return system;\n    }\n\n    public boolean isRelative() {\n        return relative;\n    }\n\n    public boolean isRelativeToParent() {\n        return relativeToParent;\n    }\n\n    @Override\n    public String toString() {\n        return literal;\n    }\n\n    public double absoluteFactor() {\n        return unit * Math.pow(factor, power);\n    }\n\n    public double to(CSSLengthUnit other) {\n        Objects.requireNonNull(other, \"Target CSS length unit must not be null\");\n        if (this.isRelative()) {\n            throw new IllegalArgumentException(\"Can not convert from a relative unit\");\n        }\n        if (other.isRelative()) {\n            throw new IllegalArgumentException(\"Can not convert to a relative unit\");\n        }\n        return absoluteFactor() / other.absoluteFactor();\n    }\n\n    /**\n     * 单位字面值转为单位\n     *\n     * @param literal 单位字面值\n     * @return 长度单位\n     */\n    public static CSSLengthUnit of(String literal) {\n        return LITERAL_MAP.get(literal);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/CSSStyleUtils.java",
    "content": "package org.ddr.poi.html.util;\n\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\nimport com.steadystate.css.dom.CSSValueImpl;\nimport com.steadystate.css.dom.Property;\nimport com.steadystate.css.parser.CSSOMParser;\nimport com.steadystate.css.parser.SACParserCSS3;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.math.NumberUtils;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.w3c.css.sac.InputSource;\nimport org.w3c.dom.css.CSSValue;\n\nimport java.io.IOException;\nimport java.io.StringReader;\n\n/**\n * CSS样式相关的工具类\n *\n * @author Draco\n * @since 2021-10-11\n */\npublic class CSSStyleUtils {\n    private static final Logger log = LoggerFactory.getLogger(CSSStyleUtils.class);\n\n    /**\n     * 空样式\n     */\n    public static final CSSStyleDeclarationImpl EMPTY_STYLE = new EmptyCSSStyle();\n\n    /**\n     * 样式是否为空\n     *\n     * @param style 样式声明\n     * @return 是否为空\n     */\n    public static boolean isEmpty(CSSStyleDeclarationImpl style) {\n        return style == null || EMPTY_STYLE.equals(style) || style.getProperties().isEmpty();\n    }\n\n    /**\n     * CSS解析器\n     */\n    public static CSSOMParser newParser() {\n        return new CSSOMParser(new SACParserCSS3());\n    }\n\n    /**\n     * 解析行内样式\n     *\n     * @param inlineStyle 行内样式声明\n     * @return 样式\n     */\n    public static CSSStyleDeclarationImpl parse(String inlineStyle) {\n        if (StringUtils.isBlank(inlineStyle)) {\n            return new CSSStyleDeclarationImpl();\n        }\n        try (StringReader sr = new StringReader(inlineStyle)) {\n            return (CSSStyleDeclarationImpl) newParser().parseStyleDeclaration(new InputSource(sr));\n        } catch (IOException e) {\n            log.warn(\"Inline style parse error: {}\", inlineStyle, e);\n            return EMPTY_STYLE;\n        }\n    }\n\n    /**\n     * 解析样式值\n     *\n     * @param value 样式值字符串\n     * @return 样式值\n     */\n    public static CSSValue parseValue(String value) {\n        try (StringReader sr = new StringReader(value)) {\n            return newParser().parsePropertyValue(new InputSource(sr));\n        } catch (IOException e) {\n            log.warn(\"CSS value parse error: {}\", value, e);\n            return new CSSValueImpl();\n        }\n    }\n\n    /**\n     * 将样式键值对转换为样式属性\n     *\n     * @param key 样式名称\n     * @param value 样式值\n     * @return 样式属性\n     */\n    public static Property newProperty(String key, String value) {\n        return new Property(key, parseValue(value), false);\n    }\n\n    /**\n     * 分解缩写的样式\n     *\n     * @param style 样式声明\n     */\n    public static void split(CSSStyleDeclarationImpl style) {\n        for (int i = style.getProperties().size() - 1; i >= 0; i--) {\n            final Property p = style.getProperties().get(i);\n            if (p != null && p.getValue() != null) {\n                String name = p.getName().toLowerCase();\n                CSSValueImpl valueList = (CSSValueImpl) p.getValue();\n                int length = valueList.getLength();\n                // 将复合样式拆分成单属性样式\n                switch (name) {\n                    case HtmlConstants.CSS_BACKGROUND:\n                        splitBackground(valueList, length, style, i);\n                        break;\n                    case HtmlConstants.CSS_BORDER:\n                        splitBorder(valueList, length, style, i);\n                        break;\n                    case HtmlConstants.CSS_BORDER_TOP:\n                        splitBorder(valueList, length, style, i, HtmlConstants.CSS_BORDER_TOP_STYLE,\n                                HtmlConstants.CSS_BORDER_TOP_WIDTH, HtmlConstants.CSS_BORDER_TOP_COLOR);\n                        break;\n                    case HtmlConstants.CSS_BORDER_RIGHT:\n                        splitBorder(valueList, length, style, i, HtmlConstants.CSS_BORDER_RIGHT_STYLE,\n                                HtmlConstants.CSS_BORDER_RIGHT_WIDTH, HtmlConstants.CSS_BORDER_RIGHT_COLOR);\n                        break;\n                    case HtmlConstants.CSS_BORDER_BOTTOM:\n                        splitBorder(valueList, length, style, i, HtmlConstants.CSS_BORDER_BOTTOM_STYLE,\n                                HtmlConstants.CSS_BORDER_BOTTOM_WIDTH, HtmlConstants.CSS_BORDER_BOTTOM_COLOR);\n                        break;\n                    case HtmlConstants.CSS_BORDER_LEFT:\n                        splitBorder(valueList, length, style, i, HtmlConstants.CSS_BORDER_LEFT_STYLE,\n                                HtmlConstants.CSS_BORDER_LEFT_WIDTH, HtmlConstants.CSS_BORDER_LEFT_COLOR);\n                        break;\n                    case HtmlConstants.CSS_BORDER_STYLE:\n                        splitBox(valueList, length, style, i, BoxProperty.BORDER_STYLE);\n                        break;\n                    case HtmlConstants.CSS_BORDER_WIDTH:\n                        splitBox(valueList, length, style, i, BoxProperty.BORDER_WIDTH);\n                        break;\n                    case HtmlConstants.CSS_BORDER_COLOR:\n                        splitBox(valueList, length, style, i, BoxProperty.BORDER_COLOR);\n                        break;\n                    case HtmlConstants.CSS_FONT:\n                        splitFont(valueList, length, style, i);\n                        break;\n                    case HtmlConstants.CSS_MARGIN:\n                        splitBox(valueList, length, style, i, BoxProperty.MARGIN);\n                        break;\n                    case HtmlConstants.CSS_PADDING:\n                        splitBox(valueList, length, style, i, BoxProperty.PADDING);\n                        break;\n                    case HtmlConstants.CSS_LIST_STYLE:\n                        splitListStyle(valueList, length, style, i);\n                        break;\n                    case HtmlConstants.CSS_TEXT_DECORATION:\n                        splitTextDecoration(valueList, length, style, i);\n                        break;\n                }\n            }\n        }\n    }\n\n    private static void splitBackground(CSSValueImpl valueList, int length, CSSStyleDeclarationImpl style, int i) {\n        if (length == 0) {\n            String cssText = valueList.getCssText().toLowerCase();\n            String color = Colors.fromStyle(cssText, null);\n            if (color != null) {\n                style.getProperties().add(i, new Property(HtmlConstants.CSS_BACKGROUND_COLOR, valueList, false));\n            }\n        } else {\n            for (int j = 0; j < length; j++) {\n                CSSValue item = valueList.item(j);\n                String cssText = item.getCssText().toLowerCase();\n                String color = Colors.fromStyle(cssText, null);\n                if (color != null) {\n                    style.getProperties().add(i, new Property(HtmlConstants.CSS_BACKGROUND_COLOR, item, false));\n                    break;\n                }\n            }\n        }\n    }\n\n    private static void splitBorder(CSSValueImpl valueList, int length, CSSStyleDeclarationImpl style, int i) {\n        if (length == 0) {\n            String cssText = valueList.getCssText();\n            if (StringUtils.isNotBlank(cssText)) {\n                handleBorderValue(style, i, valueList, cssText);\n            }\n        } else {\n            for (int j = 0; j < length; j++) {\n                CSSValue item = valueList.item(j);\n                String value = item.getCssText();\n                handleBorderValue(style, i, item, value);\n            }\n        }\n    }\n\n    private static void splitBorder(CSSValueImpl valueList, int length, CSSStyleDeclarationImpl style, int i,\n                                    String styleProperty, String widthProperty, String colorProperty) {\n        if (length == 0) {\n            String cssText = valueList.getCssText();\n            if (StringUtils.isNotBlank(cssText)) {\n                handleBorderValue(style, i, valueList, cssText, styleProperty, widthProperty, colorProperty);\n            }\n        } else {\n            for (int j = 0; j < length; j++) {\n                CSSValue item = valueList.item(j);\n                String value = item.getCssText();\n                handleBorderValue(style, i, item, value, styleProperty, widthProperty, colorProperty);\n            }\n        }\n    }\n\n    private static void handleBorderValue(CSSStyleDeclarationImpl style, int i, CSSValue item, String value) {\n        value = value.toLowerCase();\n        if (HtmlConstants.BORDER_STYLES.contains(value)) {\n            BoxProperty.BORDER_STYLE.setValues(style, i, item);\n        } else if (NamedBorderWidth.contains(value)) {\n            BoxProperty.BORDER_WIDTH.setValues(style, i, item);\n        } else if (Character.isDigit(value.charAt(0))) {\n            CSSLength width = CSSLength.of(value);\n            if (width.isValid()) {\n                BoxProperty.BORDER_WIDTH.setValues(style, i, item);\n            }\n        } else {\n            BoxProperty.BORDER_COLOR.setValues(style, i, item);\n        }\n    }\n\n    private static void handleBorderValue(CSSStyleDeclarationImpl style, int i, CSSValue item, String value,\n                                          String styleProperty, String widthProperty, String colorProperty) {\n        value = value.toLowerCase();\n        if (HtmlConstants.BORDER_STYLES.contains(value)) {\n            style.getProperties().add(i, new Property(styleProperty, item, false));\n        } else if (NamedBorderWidth.contains(value)) {\n            style.getProperties().add(i, new Property(widthProperty, item, false));\n        } else if (Character.isDigit(value.charAt(0))) {\n            CSSLength width = CSSLength.of(value);\n            if (width.isValid()) {\n                style.getProperties().add(i, new Property(widthProperty, item, false));\n            }\n        } else {\n            style.getProperties().add(i, new Property(colorProperty, item, false));\n        }\n    }\n\n    private static void splitFont(CSSValueImpl valueList, int length, CSSStyleDeclarationImpl style, int i) {\n        if (length == 0) {\n            return;\n        }\n\n        boolean styleHandled = false;\n        boolean sizeHandled = false;\n        for (int j = 0; j < length; j++) {\n            CSSValue item = valueList.item(j);\n            String value = item.getCssText();\n            String lowerCase = value.toLowerCase();\n            if (!styleHandled && HtmlConstants.FONT_STYLES.contains(lowerCase)) {\n                style.getProperties().add(i, new Property(HtmlConstants.CSS_FONT_STYLE, item, false));\n                styleHandled = true;\n            } else if (HtmlConstants.FONT_VARIANTS.contains(lowerCase)) {\n                style.getProperties().add(i, new Property(HtmlConstants.CSS_FONT_VARIANT_CAPS, item, false));\n            } else if (HtmlConstants.FONT_WEIGHTS.contains(lowerCase) || NumberUtils.isParsable(value)) {\n                style.getProperties().add(i, new Property(HtmlConstants.CSS_FONT_WEIGHT, item, false));\n            } else if (HtmlConstants.SLASH.equals(value)) {\n                // 字号与行高分隔符\n                // https://www.w3.org/TR/CSS22/fonts.html#value-def-absolute-size\n                // xx-small, x-small, small, medium, large, x-large, xx-large, xxx-large\n                // 1,        ,        2,     3,      4,     5,       6,        7\n                // FIXME font元素由于已废弃暂不支持\n                // 长度/百分比\n                CSSValue fontSize = valueList.item(j - 1);\n                style.getProperties().add(i, new Property(HtmlConstants.CSS_FONT_SIZE, fontSize, false));\n                sizeHandled = true;\n                if (++j < length) {\n                    // 数字/长度/百分比\n                    CSSValue lineHeight = valueList.item(j);\n                    style.getProperties().add(i, new Property(HtmlConstants.CSS_LINE_HEIGHT, lineHeight, false));\n                }\n            } else if (HtmlConstants.COMMA.equals(value)) {\n                // 多个字体之间的分隔符\n                CSSValue firstFont = valueList.item(j - 1);\n                if (!sizeHandled) {\n                    CSSValue fontSize = valueList.item(j - 2);\n                    style.getProperties().add(i, new Property(HtmlConstants.CSS_FONT_SIZE, fontSize, false));\n                }\n                if (HtmlConstants.isMajorFont(firstFont.getCssText())) {\n                    style.getProperties().add(i, new Property(HtmlConstants.CSS_FONT_FAMILY, firstFont, false));\n                } else {\n                    for (j++; j < length; j++) {\n                        CSSValue fontFamily = valueList.item(j);\n                        if (HtmlConstants.isMajorFont(fontFamily.getCssText())) {\n                            style.getProperties().add(i, new Property(HtmlConstants.CSS_FONT_FAMILY, fontFamily, false));\n                            break;\n                        }\n                    }\n                }\n                break;\n            } else if (j == length - 1) {\n                // font-family在font中一定是最后出现\n                style.getProperties().add(i, new Property(HtmlConstants.CSS_FONT_FAMILY, item, false));\n            }\n        }\n    }\n\n    private static void splitBox(CSSValueImpl valueList, int length, CSSStyleDeclarationImpl style, int i,\n                                 BoxProperty boxProperty) {\n        switch (length) {\n            // 当仅一个值时实际返回长度为0\n            case 0:\n            case 1:\n                if (StringUtils.isNotBlank(valueList.getCssText())) {\n                    boxProperty.setValues(style, i, valueList);\n                }\n                break;\n            case 2:\n                boxProperty.setValues(style, i, valueList.item(0), valueList.item(1));\n                break;\n            case 3:\n                boxProperty.setValues(style, i, valueList.item(0), valueList.item(1), valueList.item(2));\n                break;\n            case 4:\n                boxProperty.setValues(style, i, valueList.item(0), valueList.item(1), valueList.item(2), valueList.item(3));\n                break;\n        }\n    }\n\n    private static void splitListStyle(CSSValueImpl valueList, int length, CSSStyleDeclarationImpl style, int i) {\n        switch (length) {\n            case 0:\n            case 1:\n                String cssText = valueList.getCssText();\n                if (StringUtils.isNotBlank(cssText)) {\n                    handleListStyleValue(style, i, cssText.toLowerCase(), valueList);\n                }\n                break;\n            default:\n                for (int j = 0; j < length; j++) {\n                    CSSValue item = valueList.item(j);\n                    String value = item.getCssText().toLowerCase();\n                    handleListStyleValue(style, i, value, item);\n                }\n        }\n    }\n\n    private static void handleListStyleValue(CSSStyleDeclarationImpl style, int i, String value, CSSValue item) {\n        if (HtmlConstants.LIST_STYLE_POSITIONS.contains(value)) {\n            style.getProperties().add(i, new Property(HtmlConstants.CSS_LIST_STYLE_POSITION, item, false));\n        } else if (!value.contains(HtmlConstants.LEFT_PARENTHESIS)) {\n            style.getProperties().add(i, new Property(HtmlConstants.CSS_LIST_STYLE_TYPE, item, false));\n        }\n    }\n\n    private static void splitTextDecoration(CSSValueImpl valueList, int length, CSSStyleDeclarationImpl style, int i) {\n        if (length == 0) {\n            style.getProperties().add(i, new Property(HtmlConstants.CSS_TEXT_DECORATION_LINE, valueList, false));\n            return;\n        }\n\n        StringBuilder lines = new StringBuilder(22);\n        for (int j = 0; j < length; j++) {\n            CSSValue item = valueList.item(j);\n            String value = item.getCssText();\n            String lowerCase = value.toLowerCase();\n            if (HtmlConstants.NONE.equals(lowerCase)) {\n                style.getProperties().add(i, new Property(HtmlConstants.CSS_TEXT_DECORATION_LINE, item, false));\n                break;\n            }\n            if (HtmlConstants.TEXT_DECORATION_LINES.contains(lowerCase)) {\n                if (lines.length() > 0) {\n                    lines.append(' ');\n                }\n                lines.append(lowerCase);\n            } else if (HtmlConstants.TEXT_DECORATION_STYLES.contains(lowerCase)) {\n                style.getProperties().add(i, new Property(HtmlConstants.CSS_TEXT_DECORATION_STYLE, item, false));\n            } else if (Colors.maybe(lowerCase)) {\n                style.getProperties().add(i, new Property(HtmlConstants.CSS_TEXT_DECORATION_COLOR, item, false));\n            }\n        }\n\n        if (lines.length() > 0) {\n            style.getProperties().add(i, newProperty(HtmlConstants.CSS_TEXT_DECORATION_LINE, lines.toString()));\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/Colors.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.util;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 颜色工具类\n *\n * @author Draco\n * @since 2021-02-23\n */\npublic class Colors {\n    private static final Logger log = LoggerFactory.getLogger(Colors.class);\n\n    private static final Map<String, String> COLOR_MAP = new HashMap<>(160);\n\n    public static final String BLACK = \"000000\";\n    public static final String WHITE = \"FFFFFF\";\n    public static final String DEFAULT_COLOR = BLACK;\n    public static final String TRANSPARENT = \"transparent\";\n\n    // https://developer.mozilla.org/zh-CN/docs/Web/CSS/color_value#%E8%89%B2%E5%BD%A9%E5%85%B3%E9%94%AE%E5%AD%97\n    static {\n        COLOR_MAP.put(\"black\", BLACK);\n        COLOR_MAP.put(\"silver\", \"C0C0C0\");\n        COLOR_MAP.put(\"gray\", \"808080\");\n        COLOR_MAP.put(\"white\", \"FFFFFF\");\n        COLOR_MAP.put(\"maroon\", \"800000\");\n        COLOR_MAP.put(\"red\", \"FF0000\");\n        COLOR_MAP.put(\"purple\", \"800080\");\n        COLOR_MAP.put(\"fuchsia\", \"FF00FF\");\n        COLOR_MAP.put(\"green\", \"008000\");\n        COLOR_MAP.put(\"lime\", \"00FF00\");\n        COLOR_MAP.put(\"olive\", \"808000\");\n        COLOR_MAP.put(\"yellow\", \"FFFF00\");\n        COLOR_MAP.put(\"navy\", \"000080\");\n        COLOR_MAP.put(\"blue\", \"0000FF\");\n        COLOR_MAP.put(\"teal\", \"008080\");\n        COLOR_MAP.put(\"aqua\", \"00FFFF\");\n        COLOR_MAP.put(\"orange\", \"FFA500\");\n        COLOR_MAP.put(\"aliceblue\", \"F0F8FF\");\n        COLOR_MAP.put(\"antiquewhite\", \"FAEBD7\");\n        COLOR_MAP.put(\"aquamarine\", \"7FFFD4\");\n        COLOR_MAP.put(\"azure\", \"F0FFFF\");\n        COLOR_MAP.put(\"beige\", \"F5F5DC\");\n        COLOR_MAP.put(\"bisque\", \"FFE4C4\");\n        COLOR_MAP.put(\"blanchedalmond\", \"FFEBCD\");\n        COLOR_MAP.put(\"blueviolet\", \"8A2BE2\");\n        COLOR_MAP.put(\"brown\", \"A52A2A\");\n        COLOR_MAP.put(\"burlywood\", \"DEB887\");\n        COLOR_MAP.put(\"cadetblue\", \"5F9EA0\");\n        COLOR_MAP.put(\"chartreuse\", \"7FFF00\");\n        COLOR_MAP.put(\"chocolate\", \"D2691E\");\n        COLOR_MAP.put(\"coral\", \"FF7F50\");\n        COLOR_MAP.put(\"cornflowerblue\", \"6495ED\");\n        COLOR_MAP.put(\"cornsilk\", \"FFF8DC\");\n        COLOR_MAP.put(\"crimson\", \"DC143C\");\n        COLOR_MAP.put(\"darkblue\", \"00008B\");\n        COLOR_MAP.put(\"darkcyan\", \"008B8B\");\n        COLOR_MAP.put(\"darkgoldenrod\", \"B8860B\");\n        COLOR_MAP.put(\"darkgray\", \"A9A9A9\");\n        COLOR_MAP.put(\"darkgreen\", \"006400\");\n        COLOR_MAP.put(\"darkgrey\", \"A9A9A9\");\n        COLOR_MAP.put(\"darkkhaki\", \"BDB76B\");\n        COLOR_MAP.put(\"darkmagenta\", \"8B008B\");\n        COLOR_MAP.put(\"darkolivegreen\", \"556B2F\");\n        COLOR_MAP.put(\"darkorange\", \"FF8C00\");\n        COLOR_MAP.put(\"darkorchid\", \"9932CC\");\n        COLOR_MAP.put(\"darkred\", \"8B0000\");\n        COLOR_MAP.put(\"darksalmon\", \"E9967A\");\n        COLOR_MAP.put(\"darkseagreen\", \"8FBC8F\");\n        COLOR_MAP.put(\"darkslateblue\", \"483D8B\");\n        COLOR_MAP.put(\"darkslategray\", \"2F4F4F\");\n        COLOR_MAP.put(\"darkslategrey\", \"2F4F4F\");\n        COLOR_MAP.put(\"darkturquoise\", \"00CED1\");\n        COLOR_MAP.put(\"darkviolet\", \"9400D3\");\n        COLOR_MAP.put(\"deeppink\", \"FF1493\");\n        COLOR_MAP.put(\"deepskyblue\", \"00BFFF\");\n        COLOR_MAP.put(\"dimgray\", \"696969\");\n        COLOR_MAP.put(\"dimgrey\", \"696969\");\n        COLOR_MAP.put(\"dodgerblue\", \"1E90FF\");\n        COLOR_MAP.put(\"firebrick\", \"B22222\");\n        COLOR_MAP.put(\"floralwhite\", \"FFFAF0\");\n        COLOR_MAP.put(\"forestgreen\", \"228B22\");\n        COLOR_MAP.put(\"gainsboro\", \"DCDCDC\");\n        COLOR_MAP.put(\"ghostwhite\", \"F8F8FF\");\n        COLOR_MAP.put(\"gold\", \"FFD700\");\n        COLOR_MAP.put(\"goldenrod\", \"DAA520\");\n        COLOR_MAP.put(\"greenyellow\", \"ADFF2F\");\n        COLOR_MAP.put(\"grey\", \"808080\");\n        COLOR_MAP.put(\"honeydew\", \"F0FFF0\");\n        COLOR_MAP.put(\"hotpink\", \"FF69B4\");\n        COLOR_MAP.put(\"indianred\", \"CD5C5C\");\n        COLOR_MAP.put(\"indigo\", \"4B0082\");\n        COLOR_MAP.put(\"ivory\", \"FFFFF0\");\n        COLOR_MAP.put(\"khaki\", \"F0E68C\");\n        COLOR_MAP.put(\"lavender\", \"E6E6FA\");\n        COLOR_MAP.put(\"lavenderblush\", \"FFF0F5\");\n        COLOR_MAP.put(\"lawngreen\", \"7CFC00\");\n        COLOR_MAP.put(\"lemonchiffon\", \"FFFACD\");\n        COLOR_MAP.put(\"lightblue\", \"ADD8E6\");\n        COLOR_MAP.put(\"lightcoral\", \"F08080\");\n        COLOR_MAP.put(\"lightcyan\", \"E0FFFF\");\n        COLOR_MAP.put(\"lightgoldenrodyellow\", \"FAFAD2\");\n        COLOR_MAP.put(\"lightgray\", \"D3D3D3\");\n        COLOR_MAP.put(\"lightgreen\", \"90EE90\");\n        COLOR_MAP.put(\"lightgrey\", \"D3D3D3\");\n        COLOR_MAP.put(\"lightpink\", \"FFB6C1\");\n        COLOR_MAP.put(\"lightsalmon\", \"FFA07A\");\n        COLOR_MAP.put(\"lightseagreen\", \"20B2AA\");\n        COLOR_MAP.put(\"lightskyblue\", \"87CEFA\");\n        COLOR_MAP.put(\"lightslategray\", \"778899\");\n        COLOR_MAP.put(\"lightslategrey\", \"778899\");\n        COLOR_MAP.put(\"lightsteelblue\", \"B0C4DE\");\n        COLOR_MAP.put(\"lightyellow\", \"FFFFE0\");\n        COLOR_MAP.put(\"limegreen\", \"32CD32\");\n        COLOR_MAP.put(\"linen\", \"FAF0E6\");\n        COLOR_MAP.put(\"mediumaquamarine\", \"66CDAA\");\n        COLOR_MAP.put(\"mediumblue\", \"0000CD\");\n        COLOR_MAP.put(\"mediumorchid\", \"BA55D3\");\n        COLOR_MAP.put(\"mediumpurple\", \"9370DB\");\n        COLOR_MAP.put(\"mediumseagreen\", \"3CB371\");\n        COLOR_MAP.put(\"mediumslateblue\", \"7B68EE\");\n        COLOR_MAP.put(\"mediumspringgreen\", \"00FA9A\");\n        COLOR_MAP.put(\"mediumturquoise\", \"48D1CC\");\n        COLOR_MAP.put(\"mediumvioletred\", \"C71585\");\n        COLOR_MAP.put(\"midnightblue\", \"191970\");\n        COLOR_MAP.put(\"mintcream\", \"F5FFFA\");\n        COLOR_MAP.put(\"mistyrose\", \"FFE4E1\");\n        COLOR_MAP.put(\"moccasin\", \"FFE4B5\");\n        COLOR_MAP.put(\"navajowhite\", \"FFDEAD\");\n        COLOR_MAP.put(\"oldlace\", \"FDF5E6\");\n        COLOR_MAP.put(\"olivedrab\", \"6B8E23\");\n        COLOR_MAP.put(\"orangered\", \"FF4500\");\n        COLOR_MAP.put(\"orchid\", \"DA70D6\");\n        COLOR_MAP.put(\"palegoldenrod\", \"EEE8AA\");\n        COLOR_MAP.put(\"palegreen\", \"98FB98\");\n        COLOR_MAP.put(\"paleturquoise\", \"AFEEEE\");\n        COLOR_MAP.put(\"palevioletred\", \"DB7093\");\n        COLOR_MAP.put(\"papayawhip\", \"FFEFD5\");\n        COLOR_MAP.put(\"peachpuff\", \"FFDAB9\");\n        COLOR_MAP.put(\"peru\", \"CD853F\");\n        COLOR_MAP.put(\"pink\", \"FFC0CB\");\n        COLOR_MAP.put(\"plum\", \"DDA0DD\");\n        COLOR_MAP.put(\"powderblue\", \"B0E0E6\");\n        COLOR_MAP.put(\"rosybrown\", \"BC8F8F\");\n        COLOR_MAP.put(\"royalblue\", \"4169E1\");\n        COLOR_MAP.put(\"saddlebrown\", \"8B4513\");\n        COLOR_MAP.put(\"salmon\", \"FA8072\");\n        COLOR_MAP.put(\"sandybrown\", \"F4A460\");\n        COLOR_MAP.put(\"seagreen\", \"2E8B57\");\n        COLOR_MAP.put(\"seashell\", \"FFF5EE\");\n        COLOR_MAP.put(\"sienna\", \"A0522D\");\n        COLOR_MAP.put(\"skyblue\", \"87CEEB\");\n        COLOR_MAP.put(\"slateblue\", \"6A5ACD\");\n        COLOR_MAP.put(\"slategray\", \"708090\");\n        COLOR_MAP.put(\"slategrey\", \"708090\");\n        COLOR_MAP.put(\"snow\", \"FFFAFA\");\n        COLOR_MAP.put(\"springgreen\", \"00FF7F\");\n        COLOR_MAP.put(\"steelblue\", \"4682B4\");\n        COLOR_MAP.put(\"tan\", \"D2B48C\");\n        COLOR_MAP.put(\"thistle\", \"D8BFD8\");\n        COLOR_MAP.put(\"tomato\", \"FF6347\");\n        COLOR_MAP.put(\"turquoise\", \"40E0D0\");\n        COLOR_MAP.put(\"violet\", \"EE82EE\");\n        COLOR_MAP.put(\"wheat\", \"F5DEB3\");\n        COLOR_MAP.put(\"whitesmoke\", \"F5F5F5\");\n        COLOR_MAP.put(\"yellowgreen\", \"9ACD32\");\n        COLOR_MAP.put(\"rebeccapurple\", \"663399\");\n    }\n\n    /**\n     * 根据颜色名称获取颜色值，未找到时返回null\n     *\n     * @param name 颜色名称\n     * @return 颜色值\n     */\n    public static String getColorByName(String name) {\n        return name == null ? null : COLOR_MAP.get(name.toLowerCase());\n    }\n\n    /**\n     * 根据颜色名称获取颜色值，未找到时返回默认颜色值\n     *\n     * @param name 颜色名称\n     * @param defaultColor 默认颜色值\n     * @return 颜色值\n     */\n    public static String getColorByName(String name, String defaultColor) {\n        return name == null ? defaultColor : COLOR_MAP.getOrDefault(name.toLowerCase(), defaultColor);\n    }\n\n    /**\n     * hsl颜色转换为颜色值 <a href=\"https://www.w3.org/TR/css-color-4/#hsl-to-rgb\">link</a>\n     *\n     * @param h hue 0 - 360\n     * @param s saturation 0 - 100\n     * @param l lightness 0 - 100\n     * @return 颜色值\n     */\n    public static String fromHSL(float h, float s, float l) {\n        float[] rgb = hsl2rgb(h, s, l);\n        return toHexString(toInt(rgb[0]), toInt(rgb[1]), toInt(rgb[2]));\n    }\n\n    private static float[] hsl2rgb(float h, float s, float l) {\n        h = h % 360;\n        if (h < 0) {\n            h += 360;\n        }\n        s /= 100;\n        l /= 100;\n        return new float[] {\n                hue2rgb(h, s, l, 0),\n                hue2rgb(h, s, l, 8),\n                hue2rgb(h, s, l, 4)\n        };\n    }\n\n    private static float hue2rgb(float h, float s, float l, int n) {\n        float k = (n + h / 30) % 12;\n        float a = s * Math.min(l, 1 - l);\n        return l - a * Math.max(-1, Math.min(k - 3, Math.min(9 - k, 1)));\n    }\n\n    /**\n     * hwb颜色转换为颜色值 <a href=\"https://www.w3.org/TR/css-color-4/#hwb-to-rgb\">link</a>\n     *\n     * @param h hue 0 - 360\n     * @param w whiteness 0 - 100\n     * @param b blackness 0 - 100\n     * @return 颜色值\n     */\n    public static String fromHWB(float h, float w, float b) {\n        w /= 100;\n        b /= 100;\n        if (w + b >= 1) {\n            int gray = toInt(w / (w + b));\n            return toHexString(gray, gray, gray);\n        }\n        float[] rgb = hsl2rgb(h, 100, 50);\n        for (int i = 0; i < 3; i++) {\n            rgb[i] *= (1 - w - b);\n            rgb[i] += w;\n        }\n        return toHexString(toInt(rgb[0]), toInt(rgb[1]), toInt(rgb[2]));\n    }\n\n    private static int toInt(float f) {\n        return (int) (f * 255 + 0.5);\n    }\n\n    private static float degrees(String s) {\n        if (s.endsWith(\"grad\")) {\n            return Float.parseFloat(s.substring(0, s.length() - 4)) * 0.9f;\n        } else if (s.endsWith(\"rad\")) {\n            return (float) (Double.parseDouble(s.substring(0, s.length() - 3)) * 180 / Math.PI);\n        } else if (s.endsWith(\"turn\")) {\n            return Float.parseFloat(s.substring(0, s.length() - 4)) * 360;\n        } else if (s.endsWith(\"deg\")) {\n            return Float.parseFloat(s.substring(0, s.length() - 3));\n        } else {\n            return Float.parseFloat(s);\n        }\n    }\n\n    /**\n     * 将RGB转换为颜色值\n     *\n     * @param r Red\n     * @param g Green\n     * @param b Blue\n     * @return 颜色值\n     */\n    public static String toHexString(int r, int g, int b) {\n        return String.format(\"%02X%02X%02X\", r, g, b);\n    }\n\n    /**\n     * 解析样式值为颜色值\n     *\n     * @param style 样式值\n     * @param defaultColor 默认颜色值\n     * @return 颜色值\n     */\n    public static String fromStyle(String style, String defaultColor) {\n        if (StringUtils.isBlank(style)) {\n            return defaultColor;\n        }\n        // Word中不支持alpha通道，直接忽略\n        if (style.startsWith(HtmlConstants.SHARP)) {\n            String hex = style.substring(1).trim();\n            if (hex.length() == 3 || hex.length() == 4) {\n                char[] chars = new char[6];\n                for (int i = 0; i < 6; i++) {\n                    chars[i] = hex.charAt(i >> 1);\n                }\n                return String.valueOf(chars);\n            } else if (hex.length() >= 6) {\n                return hex.substring(0, 6);\n            } else {\n                warn(style);\n            }\n        } else if (style.startsWith(\"rgb\")) {\n//            color: rgb(34, 12, 64, 0.6);\n//            color: rgba(34, 12, 64, 0.6);\n//            color: rgb(34 12 64 / 0.6);\n//            color: rgba(34 12 64 / 0.3);\n//            color: rgb(34.0 12 64 / 60%);\n//            color: rgba(34.6 12 64 / 30%);\n            String[] array = StringUtils.split(StringUtils.substringBetween(style, \"(\", \")\"), \", /\");\n            if (array != null && array.length >= 3) {\n                try {\n                    return toHexString((int) Float.parseFloat(array[0]), (int) Float.parseFloat(array[1]), (int) Float.parseFloat(array[2]));\n                } catch (NumberFormatException e) {\n                    warn(style);\n                }\n            } else {\n                warn(style);\n            }\n        } else if (style.startsWith(\"hsl\")) {\n//            color: hsl(30, 100%, 50%, 0.6);\n//            color: hsla(30, 100%, 50%, 0.6);\n//            color: hsl(30 100% 50% / 0.6);\n//            color: hsla(30 100% 50% / 0.6);\n//            color: hsl(30.0 100% 50% / 60%);\n//            color: hsla(30.2 100% 50% / 60%);\n            String[] array = StringUtils.split(StringUtils.substringBetween(style, \"(\", \")\"), \", /\");\n            if (array != null && array.length >= 3\n                    && array[1].endsWith(HtmlConstants.PERCENT) && array[2].endsWith(HtmlConstants.PERCENT)) {\n                float h = degrees(array[0]);\n                String ss = array[1];\n                float s = Float.parseFloat(ss.substring(0, ss.length() - 1)) / 100;\n                String ls = array[2];\n                float l = Float.parseFloat(ls.substring(0, ls.length() - 1)) / 100;\n                return fromHSL(h, s, l);\n            } else {\n                warn(style);\n            }\n        } else if (style.startsWith(\"hwb\")) {\n//            color: hwb(90 10% 10%);\n//            color: hwb(90 10% 10% / 0.5);\n//            color: hwb(90deg 10% 10%);\n//            color: hwb(1.5708rad 60% 0%);\n//            color: hwb(0.25turn 0% 40% / 50%);\n            String[] array = StringUtils.split(StringUtils.substringBetween(style, \"(\", \")\"), \", /\");\n            if (array != null && array.length >= 3\n                    && array[1].endsWith(HtmlConstants.PERCENT) && array[2].endsWith(HtmlConstants.PERCENT)) {\n                float h = degrees(array[0]);\n                String ws = array[1];\n                float w = Float.parseFloat(ws.substring(0, ws.length() - 1)) / 100;\n                String bs = array[2];\n                float b = Float.parseFloat(bs.substring(0, bs.length() - 1)) / 100;\n                return fromHWB(h, w, b);\n            } else {\n                warn(style);\n            }\n        } else {\n            return getColorByName(style, defaultColor);\n        }\n\n        return defaultColor;\n    }\n\n    /**\n     * 解析样式值为颜色值，解析失败时返回默认颜色值（黑色）\n     *\n     * @param style 样式值\n     * @return 颜色值\n     */\n    public static String fromStyle(String style) {\n        return fromStyle(style, DEFAULT_COLOR);\n    }\n\n    private static void warn(String style) {\n        log.warn(\"Illegal color: {}\", style);\n    }\n\n    /**\n     * 判断样式值是否可能为颜色\n     *\n     * @param style 样式值\n     * @return 是否可能为颜色\n     */\n    public static boolean maybe(String style) {\n        return style.startsWith(HtmlConstants.SHARP)\n                || style.startsWith(\"rgb\")\n                || style.startsWith(\"hsl\")\n                || style.startsWith(\"hwb\")\n                || COLOR_MAP.containsKey(style);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/ColumnStyle.java",
    "content": "package org.ddr.poi.html.util;\n\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\n\n/**\n * 表格列样式定义\n *\n * @author Draco\n * @since 2022-10-28\n */\npublic class ColumnStyle {\n    private CSSStyleDeclarationImpl style;\n    private CSSLength width;\n\n    public ColumnStyle(CSSStyleDeclarationImpl style, CSSLength width) {\n        this.style = style;\n        this.width = width;\n    }\n\n    public CSSStyleDeclarationImpl getStyle() {\n        return style;\n    }\n\n    public CSSLength getWidth() {\n        return width;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/EmptyCSSStyle.java",
    "content": "package org.ddr.poi.html.util;\n\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\nimport com.steadystate.css.dom.Property;\nimport org.w3c.dom.DOMException;\n\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * 空样式\n *\n * @author Draco\n * @since 2022-10-21\n */\npublic class EmptyCSSStyle extends CSSStyleDeclarationImpl {\n    @Override\n    public void setProperties(List<Property> properties) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public List<Property> getProperties() {\n        return Collections.unmodifiableList(super.getProperties());\n    }\n\n    @Override\n    public void setProperty(String propertyName, String value, String priority) throws DOMException {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void setCssText(String cssText) throws DOMException {\n        throw new UnsupportedOperationException();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/InlineStyle.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.util;\n\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\n\n/**\n * 行内样式封装类\n *\n * @author Draco\n * @since 2021-03-18\n */\npublic final class InlineStyle {\n    /**\n     * 样式声明\n     */\n    private final CSSStyleDeclarationImpl declaration;\n    /**\n     * 是否为区块元素\n     */\n    private final boolean block;\n\n    public InlineStyle(CSSStyleDeclarationImpl declaration, boolean block) {\n        this.declaration = declaration;\n        this.block = block;\n    }\n\n    public CSSStyleDeclarationImpl getDeclaration() {\n        return this.declaration;\n    }\n\n    public boolean isBlock() {\n        return this.block;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/JsoupUtils.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.util;\n\nimport org.ddr.poi.html.HtmlConstants;\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.parser.CustomHtmlTreeBuilder;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.select.Elements;\n\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.function.Predicate;\n\n/**\n * JSoup工具类\n *\n * @author Draco\n * @since 2021-03-03\n */\npublic class JsoupUtils {\n    /**\n     * 选取符合条件的子元素到目标集合中\n     *\n     * @param collection 目标集合\n     * @param parent 父元素\n     * @param predicate 条件\n     */\n    public static void selectChildren(Elements collection, Element parent, Predicate<Element> predicate) {\n        for (Node node : parent.childNodes()) {\n            if (node instanceof Element) {\n                Element child = ((Element) node);\n                if (predicate.test(child)) {\n                    collection.add(child);\n                }\n            }\n        }\n    }\n\n    /**\n     * 选取指定标签的子元素\n     *\n     * @param parent 父元素\n     * @param tag 标签名称，小写\n     * @return 子元素集合\n     */\n    public static Elements children(Element parent, String tag) {\n        Elements elements = new Elements();\n        selectChildren(elements, parent, c -> c.normalName().equals(tag));\n        return elements;\n    }\n\n    /**\n     * 选取指定标签的子元素\n     *\n     * @param parent 父元素\n     * @param tags 多种标签名称，小写\n     * @return 子元素集合\n     */\n    public static Elements children(Element parent, String... tags) {\n        Elements elements = new Elements();\n        Set<String> targets = new HashSet<>(Arrays.asList(tags));\n        selectChildren(elements, parent, c -> targets.contains(c.normalName()));\n        return elements;\n    }\n\n    /**\n     * 选取第一个指定标签的子元素\n     *\n     * @param parent 父元素\n     * @param tag 标签名称，小写\n     * @return 子元素\n     */\n    public static Element firstChild(Element parent, String tag) {\n        for (Node node : parent.childNodes()) {\n            if (node instanceof Element) {\n                Element child = ((Element) node);\n                if (child.normalName().equals(tag)) {\n                    return child;\n                }\n            }\n        }\n        return null;\n    }\n\n    /**\n     * 选取表格的所有行元素\n     *\n     * @param parent 表格元素\n     * @return 行元素集合\n     */\n    public static Elements childRows(Element parent) {\n        Elements elements = new Elements();\n        for (Node node : parent.childNodes()) {\n            if (node instanceof Element) {\n                Element child = ((Element) node);\n                if (HtmlConstants.TAG_TR.equals(child.normalName())) {\n                    // 直接位于table标签下\n                    elements.add(child);\n                } else {\n                    // 可能位于thead/tbody/tfoot标签下，选取直接子元素避免受嵌套表格影响\n                    selectChildren(elements, child, c -> HtmlConstants.TAG_TR.equals(c.normalName()));\n                }\n            }\n        }\n        return elements;\n    }\n\n    /**\n     * @see org.jsoup.Jsoup#parseBodyFragment(String)\n     * @see org.jsoup.parser.Parser#parseBodyFragment(String, String)\n     */\n    public static Document parse(String html) {\n        CustomHtmlTreeBuilder treeBuilder = new CustomHtmlTreeBuilder();\n        return Jsoup.parse(html, new Parser(treeBuilder));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/ListStyle.java",
    "content": "package org.ddr.poi.html.util;\n\n/**\n * 列表样式\n *\n * @author Draco\n * @since 2024-09-12\n */\npublic class ListStyle {\n    private final ListStyleType numberFormat;\n\n    private final boolean hanging;\n\n    private final int left;\n\n    private final int right;\n\n    public ListStyle(ListStyleType numberFormat, boolean hanging, int left, int right) {\n        this.numberFormat = numberFormat;\n        this.hanging = hanging;\n        this.left = left;\n        this.right = right;\n    }\n\n    public ListStyleType getNumberFormat() {\n        return numberFormat;\n    }\n\n    public boolean isHanging() {\n        return hanging;\n    }\n\n    public int getLeft() {\n        return left;\n    }\n\n    public int getRight() {\n        return right;\n    }\n\n    @Override\n    public String toString() {\n        return \"ListStyle{\" +\n                \"numberFormat=\" + numberFormat +\n                \", hanging=\" + hanging +\n                \", left=\" + left +\n                \", right=\" + right +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/ListStyleType.java",
    "content": "package org.ddr.poi.html.util;\n\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STNumberFormat;\n\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * 列表项样式\n *\n * @author Draco\n * @since 2022-02-08\n */\npublic interface ListStyleType {\n    String SYMBOL_DISC = \"\\uF06C\"; // •⚫\n    String SYMBOL_CIRCLE = \"\\uF0A1\"; // ◦⚪\n    String SYMBOL_DISCLOSURE_CLOSED = \"\\uF075\"; // ▸\n    String SYMBOL_DISCLOSURE_OPEN = \"\\uF071\"; // ▾\n    String SYMBOL_SQUARE = \"\\uF06E\"; // ▪\n\n    String FONT_WINGDINGS = \"Wingdings\";\n    String FONT_WINGDINGS_3 = \"Wingdings 3\";\n\n    String getName();\n\n    STNumberFormat.Enum getFormat();\n\n    /**\n     * string: Custom symbol empty string: System counting style null: No symbol\n     */\n    String getText();\n\n    String getFont();\n\n    enum Unordered implements ListStyleType {\n        DISC(\"disc\", STNumberFormat.BULLET, SYMBOL_DISC, FONT_WINGDINGS),\n        CIRCLE(\"circle\", STNumberFormat.BULLET, SYMBOL_CIRCLE, FONT_WINGDINGS),\n        DECIMAL(\"decimal\", STNumberFormat.DECIMAL, \"\", null),\n        DISCLOSURE_CLOSED(\"disclosure-closed\", STNumberFormat.BULLET, SYMBOL_DISCLOSURE_CLOSED, FONT_WINGDINGS_3),\n        DISCLOSURE_OPEN(\"disclosure-open\", STNumberFormat.BULLET, SYMBOL_DISCLOSURE_OPEN, FONT_WINGDINGS_3),\n        SQUARE(\"square\", STNumberFormat.BULLET, SYMBOL_SQUARE, FONT_WINGDINGS),\n        NONE(\"none\", STNumberFormat.NONE, null, null);\n\n        private static final Map<String, ListStyleType> TYPE_MAP = Arrays.stream(values())\n                .collect(Collectors.toMap(Unordered::getName, Function.identity()));\n\n        private final String name;\n        private final STNumberFormat.Enum format;\n        private final String text;\n        private final String font;\n\n        Unordered(String name, STNumberFormat.Enum format, String text, String font) {\n            this.name = name;\n            this.format = format;\n            this.text = text;\n            this.font = font;\n        }\n\n        @Override\n        public String getName() {\n            return name;\n        }\n\n        @Override\n        public STNumberFormat.Enum getFormat() {\n            return format;\n        }\n\n        @Override\n        public String getText() {\n            return text;\n        }\n\n        @Override\n        public String getFont() {\n            return font;\n        }\n\n        public static ListStyleType of(String type) {\n            return TYPE_MAP.getOrDefault(type, DISC);\n        }\n    }\n\n    /**\n     * https://www.w3.org/TR/css-counter-styles-3/#predefined-counters\n     */\n    enum Ordered implements ListStyleType {\n        /* Numeric */\n        DECIMAL(\"decimal\", STNumberFormat.DECIMAL, \"\", null),\n        DECIMAL_LEADING_ZERO(\"decimal-leading-zero\", STNumberFormat.DECIMAL_ZERO, \"\", null),\n        CJK_DECIMAL(\"cjk-decimal\", STNumberFormat.TAIWANESE_DIGITAL, \"\", null),\n        HEBREW(\"hebrew\", STNumberFormat.HEBREW_1, \"\", null),\n        LOWER_ROMAN(\"lower-roman\", STNumberFormat.LOWER_ROMAN, \"\", null),\n        UPPER_ROMAN(\"upper-roman\", STNumberFormat.UPPER_ROMAN, \"\", null),\n        THAI(\"thai\", STNumberFormat.THAI_NUMBERS, \"\", null),\n        /* Alphabetic */\n        LOWER_ALPHA(\"lower-alpha\", STNumberFormat.LOWER_LETTER, \"\", null),\n        LOWER_LATIN(\"lower-latin\", STNumberFormat.LOWER_LETTER, \"\", null),\n        UPPER_ALPHA(\"upper-alpha\", STNumberFormat.UPPER_LETTER, \"\", null),\n        UPPER_LATIN(\"upper-latin\", STNumberFormat.UPPER_LETTER, \"\", null),\n        // hiragana\n        // hiragana-iroha\n        KATAKANA(\"katakana\", STNumberFormat.AIUEO_FULL_WIDTH, \"\", null),\n        KATAKANA_IROHA(\"katakana-iroha\", STNumberFormat.IROHA_FULL_WIDTH, \"\", null),\n        /* Symbolic */\n        DISC(\"disc\", STNumberFormat.BULLET, SYMBOL_DISC, FONT_WINGDINGS),\n        CIRCLE(\"circle\", STNumberFormat.BULLET, SYMBOL_CIRCLE, FONT_WINGDINGS),\n        DISCLOSURE_CLOSED(\"disclosure-closed\", STNumberFormat.BULLET, SYMBOL_DISCLOSURE_CLOSED, FONT_WINGDINGS_3),\n        DISCLOSURE_OPEN(\"disclosure-open\", STNumberFormat.BULLET, SYMBOL_DISCLOSURE_OPEN, FONT_WINGDINGS_3),\n        SQUARE(\"square\", STNumberFormat.BULLET, SYMBOL_SQUARE, FONT_WINGDINGS),\n        /* Longhand East Asian */\n        JAPANESE_INFORMAL(\"japanese-informal\", STNumberFormat.JAPANESE_COUNTING, \"\", null),\n        JAPANESE_FORMAL(\"japanese-formal\", STNumberFormat.JAPANESE_LEGAL, \"\", null),\n        KOREAN_HANGUL_FORMAL(\"korean-hangul-formal\", STNumberFormat.KOREAN_COUNTING, \"\", null),\n        // partial matching\n        KOREAN_HANJA_INFORMAL(\"korean-hanja-informal\", STNumberFormat.KOREAN_DIGITAL_2, \"\", null),\n        // partial matching\n        KOREAN_HANJA_FORMAL(\"korean-hanja-formal\", STNumberFormat.CHINESE_LEGAL_SIMPLIFIED, \"\", null),\n        SIMP_CHINESE_INFORMAL(\"simp-chinese-informal\", STNumberFormat.CHINESE_COUNTING, \"\", null),\n        SIMP_CHINESE_FORMAL(\"simp-chinese-formal\", STNumberFormat.CHINESE_LEGAL_SIMPLIFIED, \"\", null),\n        TRAD_CHINESE_INFORMAL(\"trad-chinese-informal\", STNumberFormat.TAIWANESE_COUNTING, \"\", null),\n        // partial matching\n        TRAD_CHINESE_FORMAL(\"trad-chinese-formal\", STNumberFormat.CHINESE_LEGAL_SIMPLIFIED, \"\", null),\n        CJK_IDEOGRAPHIC(\"cjk-ideographic\", STNumberFormat.CHINESE_LEGAL_SIMPLIFIED, \"\", null),\n        /* ol type */\n        ONE(\"1\", STNumberFormat.DECIMAL, \"\", null),\n        LOWER_A(\"a\", STNumberFormat.LOWER_LETTER, \"\", null),\n        UPPER_A(\"A\", STNumberFormat.UPPER_LETTER, \"\", null),\n        LOWER_I(\"i\", STNumberFormat.LOWER_ROMAN, \"\", null),\n        UPPER_I(\"I\", STNumberFormat.UPPER_ROMAN, \"\", null),\n        NONE(\"none\", STNumberFormat.NONE, null, null);\n\n        private static final Map<String, ListStyleType> TYPE_MAP = Arrays.stream(values())\n                .collect(Collectors.toMap(Ordered::getName, Function.identity()));\n\n        private final String name;\n        private final STNumberFormat.Enum format;\n        private final String text;\n        private final String font;\n\n        Ordered(String name, STNumberFormat.Enum format, String text, String font) {\n            this.name = name;\n            this.format = format;\n            this.text = text;\n            this.font = font;\n        }\n\n        @Override\n        public String getName() {\n            return name;\n        }\n\n        @Override\n        public STNumberFormat.Enum getFormat() {\n            return format;\n        }\n\n        @Override\n        public String getText() {\n            return text;\n        }\n\n        @Override\n        public String getFont() {\n            return font;\n        }\n\n        public static ListStyleType of(String type) {\n            return TYPE_MAP.getOrDefault(type, DECIMAL);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/NamedBorderWidth.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.util;\n\nimport org.ddr.poi.html.HtmlConstants;\n\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * 已命名的边框宽度，宽度值与chrome一致\n *\n * @author Draco\n * @since 2021-03-29\n */\npublic enum NamedBorderWidth {\n    THIN(HtmlConstants.THIN, 1),\n    MEDIUM(HtmlConstants.MEDIUM, 3),\n    THICK(HtmlConstants.THICK, 5);\n\n    private final String name;\n    private final CSSLength width;\n    private static final Map<String, NamedBorderWidth> NAMED_MAP = Arrays.stream(NamedBorderWidth.values()).collect(\n            Collectors.toMap(NamedBorderWidth::getName, Function.identity())\n    );\n\n    NamedBorderWidth(String name, int px) {\n        this.name = name;\n        this.width = new CSSLength(px, CSSLengthUnit.PX);\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public CSSLength getWidth() {\n        return width;\n    }\n\n    public static NamedBorderWidth of(String name) {\n        return NAMED_MAP.get(name);\n    }\n\n    public static boolean contains(String name) {\n        return NAMED_MAP.containsKey(name);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/NamedFontSize.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.util;\n\nimport org.ddr.poi.html.HtmlConstants;\n\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * 已命名的字号\n *\n * @author Draco\n * @since 2021-02-25\n */\npublic enum NamedFontSize {\n    XX_SMALL(HtmlConstants.XX_SMALL, 6d),\n    X_SMALL(HtmlConstants.X_SMALL, 7.5d),\n    SMALL(HtmlConstants.SMALL, 10d),\n    MEDIUM(HtmlConstants.MEDIUM, 12d),\n    LARGE(HtmlConstants.LARGE, 13.5d),\n    X_LARGE(HtmlConstants.X_LARGE, 18d),\n    XX_LARGE(HtmlConstants.XX_LARGE, 24d),\n    XXX_LARGE(HtmlConstants.XXX_LARGE, 36d);\n\n    private final String name;\n    private final CSSLength size;\n    private static final Map<String, NamedFontSize> NAMED_MAP = Arrays.stream(NamedFontSize.values()).collect(\n            Collectors.toMap(NamedFontSize::getName, Function.identity())\n    );\n\n    NamedFontSize(String name, double pt) {\n        this.name = name;\n        this.size = new CSSLength(pt, CSSLengthUnit.PT);\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public CSSLength getSize() {\n        return size;\n    }\n\n    public static NamedFontSize of(String name) {\n        return NAMED_MAP.get(name);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/NumberingContext.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.util;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.poi.xwpf.usermodel.BodyType;\nimport org.apache.poi.xwpf.usermodel.XWPFAbstractNum;\nimport org.apache.poi.xwpf.usermodel.XWPFDocument;\nimport org.apache.poi.xwpf.usermodel.XWPFNumbering;\nimport org.apache.poi.xwpf.usermodel.XWPFParagraph;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTAbstractNum;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFonts;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTInd;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTLvl;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STHint;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STJc;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STLevelSuffix;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STMultiLevelType;\n\nimport java.math.BigInteger;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TreeMap;\n\n/**\n * 列表上下文\n *\n * @author Draco\n * @since 2021-02-19\n */\npublic class NumberingContext {\n    /**\n     * 每级缩进\n     */\n    private static final int INDENT = 360;\n    private static final int HANGING = 360;\n    private final XWPFDocument document;\n    private int indent = INDENT;\n    private int hanging = HANGING;\n    private STLevelSuffix.Enum spacing;\n    private int nextAbstractNumberId;\n    private int nextNumberingLevel;\n\n    private List<ListStyle> listStyles;\n    private final TreeMap<String, BigInteger> numberIdMap = new TreeMap<>(Collections.reverseOrder());\n    private List<XWPFParagraph> numberingParagraphs;\n\n    public NumberingContext(XWPFDocument document) {\n        this.document = document;\n    }\n\n    /**\n     * 开始新的列表\n     *\n     * @param listStyle  列表样式\n     */\n    public void startLevel(ListStyle listStyle) {\n        int level = nextNumberingLevel++;\n        if (level == 0) {\n            numberingParagraphs = new ArrayList<>(8);\n            listStyles = new ArrayList<>(4);\n        }\n        listStyles.add(listStyle);\n    }\n\n    /**\n     * 结束当前列表\n     */\n    public void endLevel() {\n        nextNumberingLevel--;\n\n        String key = getFormatKey();\n        BigInteger numberId = getNumberId(key);\n\n        BigInteger currentLevel = BigInteger.valueOf(nextNumberingLevel);\n        for (int i = numberingParagraphs.size() - 1; i >= 0; i--) {\n            XWPFParagraph paragraph = numberingParagraphs.get(i);\n            if (currentLevel.equals(paragraph.getNumIlvl())) {\n                paragraph.setNumID(numberId);\n                numberingParagraphs.remove(i);\n            } else {\n                break;\n            }\n        }\n        if (!listStyles.isEmpty()) {\n            listStyles.remove(listStyles.size() - 1);\n        }\n\n        if (nextNumberingLevel == 0) {\n            numberingParagraphs = null;\n            listStyles = null;\n            numberIdMap.clear();\n        }\n    }\n\n    /**\n     * 新增段落\n     *\n     * @param paragraph 段落\n     */\n    public void add(XWPFParagraph paragraph) {\n        if (numberingParagraphs == null) {\n            // startLevel method not called, fallback as <div> or <p>\n            return;\n        }\n        paragraph.setNumILvl(BigInteger.valueOf(nextNumberingLevel - 1));\n        if (paragraph.getPartType() == BodyType.TABLECELL) {\n            CTPPr pPr = RenderUtils.getPPr(paragraph.getCTP());\n            if (!pPr.isSetInd()) {\n                RenderUtils.getInd(pPr).setFirstLine(BigInteger.ZERO);\n            }\n        }\n        numberingParagraphs.add(paragraph);\n    }\n\n    /**\n     * 获取列表ID\n     *\n     * @param key Key\n     * @return 列表ID\n     */\n    private BigInteger getNumberId(String key) {\n        BigInteger numberId = null;\n        for (Map.Entry<String, BigInteger> entry : numberIdMap.entrySet()) {\n            if (entry.getKey().startsWith(key)) {\n                numberId = entry.getValue();\n                break;\n            }\n        }\n        if (numberId == null) {\n            XWPFNumbering numbering = document.createNumbering();\n            while (true) {\n                BigInteger abstractNumberId = BigInteger.valueOf(nextAbstractNumberId++);\n                XWPFAbstractNum abstractNum = numbering.getAbstractNum(abstractNumberId);\n                if (abstractNum == null) {\n                    CTAbstractNum ctAbstractNum = CTAbstractNum.Factory.newInstance();\n                    ctAbstractNum.setAbstractNumId(abstractNumberId);\n                    ctAbstractNum.addNewMultiLevelType().setVal(STMultiLevelType.HYBRID_MULTILEVEL);\n\n                    for (int i = 0; i < listStyles.size(); i++) {\n                        ListStyle listStyle = listStyles.get(i);\n                        ListStyleType listStyleType = listStyle.getNumberFormat();\n                        CTLvl cTLvl = ctAbstractNum.addNewLvl();\n                        CTInd ind = cTLvl.addNewPPr().addNewInd();\n                        long left = indent * i + listStyle.getLeft();\n                        long right = listStyle.getRight();\n                        for (int j = 0; j < i; j++) {\n                            ListStyle previous = listStyles.get(j);\n                            left += previous.getLeft();\n                            right += previous.getRight();\n                        }\n                        ind.setLeft(BigInteger.valueOf(left));\n                        ind.setRight(BigInteger.valueOf(right));\n                        if (listStyle.isHanging()) {\n                            ind.setHanging(BigInteger.valueOf(hanging));\n                        }\n\n                        cTLvl.addNewNumFmt().setVal(listStyleType.getFormat());\n                        cTLvl.addNewLvlText().setVal(getLevelText(listStyleType, i));\n                        cTLvl.addNewStart().setVal(BigInteger.ONE);\n                        cTLvl.setIlvl(BigInteger.valueOf(i));\n                        cTLvl.addNewLvlJc().setVal(STJc.LEFT);\n                        if (StringUtils.isNotBlank(listStyleType.getFont())) {\n                            CTFonts ctFonts = cTLvl.addNewRPr().addNewRFonts();\n                            ctFonts.setAscii(listStyleType.getFont());\n                            ctFonts.setHAnsi(listStyleType.getFont());\n                            ctFonts.setHint(STHint.DEFAULT);\n                        }\n                        if (spacing != null) {\n                            cTLvl.addNewSuff().setVal(spacing);\n                        }\n                    }\n\n                    numbering.addAbstractNum(new XWPFAbstractNum(ctAbstractNum, numbering));\n                    numberId = numbering.addNum(abstractNumberId);\n\n                    numberIdMap.put(key, numberId);\n                    break;\n                }\n            }\n        }\n        return numberId;\n    }\n\n    private String getLevelText(ListStyleType listStyleType, int i) {\n        if (listStyleType.getText() == null) {\n            return \"\";\n        }\n        if (listStyleType.getText().length() == 0) {\n            return getOrderedLevelText(i);\n        }\n        return listStyleType.getText();\n    }\n\n    /**\n     * 获取有序列表项的序号格式\n     *\n     * @param i 索引，从0开始\n     * @return 序号格式\n     */\n    private String getOrderedLevelText(int i) {\n        return \"%\" + (i + 1) + \".\";\n    }\n\n    private String getFormatKey() {\n        StringBuilder sb = new StringBuilder();\n        for (ListStyle listStyle : listStyles) {\n            sb.append(listStyle.getNumberFormat().getName()).append(StringUtils.SPACE);\n        }\n        return sb.toString();\n    }\n\n    public void setIndent(int indent) {\n        if (indent >= 0) {\n            this.indent = indent;\n        }\n    }\n\n    public void setHanging(int hanging) {\n        if (hanging >= 0) {\n            this.hanging = hanging;\n        }\n    }\n\n    public void setSpacing(STLevelSuffix.Enum spacing) {\n        this.spacing = spacing;\n    }\n\n    public boolean contains(XWPFParagraph paragraph) {\n        return numberingParagraphs != null && numberingParagraphs.contains(paragraph);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/RenderUtils.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.util;\n\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.math.NumberUtils;\nimport org.apache.poi.util.Units;\nimport org.apache.poi.xwpf.usermodel.BodyType;\nimport org.apache.poi.xwpf.usermodel.IBody;\nimport org.apache.poi.xwpf.usermodel.ParagraphAlignment;\nimport org.apache.poi.xwpf.usermodel.TableRowAlign;\nimport org.apache.poi.xwpf.usermodel.TableWidthType;\nimport org.apache.poi.xwpf.usermodel.XWPFDocument;\nimport org.apache.poi.xwpf.usermodel.XWPFParagraph;\nimport org.apache.poi.xwpf.usermodel.XWPFTable;\nimport org.apache.poi.xwpf.usermodel.XWPFTableCell;\nimport org.ddr.poi.html.HtmlConstants;\nimport org.ddr.poi.html.HtmlRenderContext;\nimport org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D;\nimport org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTAnchor;\nimport org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBorder;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTColor;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDrawing;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTInd;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTJc;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPBdr;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRPr;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTShd;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSpacing;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTStyle;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblBorders;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblPr;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcBorders;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcMar;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTUnderline;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STLineSpacingRule;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STUnderline;\n\nimport java.math.BigInteger;\nimport java.util.function.Function;\n\n/**\n * 渲染相关的工具类\n *\n * @author Draco\n * @since 2021-02-08\n */\npublic class RenderUtils {\n    /**\n     * Word中字号下拉列表对应的值\n     */\n    public static final int[] FONT_SIZE_IN_HALF_POINTS = {10, 11, 13, 15, 18, 21, 24, 28, 30, 32, 36, 44, 48, 52, 72, 84, 96, 144};\n    /**\n     * 边框宽度每像素对应值\n     */\n    public static final int BORDER_WIDTH_PER_PX = 4;\n    /**\n     * 最小边框宽度\n     */\n    public static final long MIN_BORDER_WIDTH = 2;\n    /**\n     * 最大边框宽度\n     */\n    public static final long MAX_BORDER_WIDTH = 96;\n\n    /**\n     * 表格单元格边距\n     */\n    public static final int TABLE_CELL_MARGIN = 108;\n    /**\n     * 段落行距系数\n     */\n    public static final int SPACING_FACTOR = 240;\n    /**\n     * 默认页面宽度 A4 portrait\n     */\n    public static final int A4_WIDTH = 11906;\n    /**\n     * 默认页面高度 A4 portrait\n     */\n    public static final int A4_HEIGHT = 16838;\n\n    // Defined in [ Word 2010 look.dotx ]\n    /**\n     * 默认顶边距\n     */\n    public static final int DEFAULT_TOP_MARGIN = 1440;\n    /**\n     * 默认底边距\n     */\n    public static final int DEFAULT_BOTTOM_MARGIN = 1440;\n    /**\n     * 默认左边距\n     */\n    public static final int DEFAULT_LEFT_MARGIN = 1440;\n    /**\n     * 默认右边距\n     */\n    public static final int DEFAULT_RIGHT_MARGIN = 1440;\n\n    /**\n     * 文本对齐值映射\n     *\n     * @param textAlign 文本对齐样式值\n     * @return Word文本对齐枚举\n     */\n    public static ParagraphAlignment align(String textAlign) {\n        if (StringUtils.isBlank(textAlign)) {\n            return null;\n        }\n        switch (textAlign.toLowerCase()) {\n            case HtmlConstants.START:\n            case HtmlConstants.LEFT:\n                return ParagraphAlignment.LEFT;\n            case HtmlConstants.END:\n            case HtmlConstants.RIGHT:\n                return ParagraphAlignment.RIGHT;\n            case HtmlConstants.CENTER:\n                return ParagraphAlignment.CENTER;\n            case HtmlConstants.JUSTIFY:\n            case HtmlConstants.JUSTIFY_ALL:\n                return ParagraphAlignment.BOTH;\n            default:\n                return null;\n        }\n    }\n\n    /**\n     * 下划线样式映射\n     *\n     * @param textDecorationStyle 下划线样式值\n     * @return Word下划线样式\n     */\n    public static STUnderline.Enum underline(String textDecorationStyle) {\n        switch (textDecorationStyle) {\n//            case HtmlConstants.SOLID:\n//                return STUnderline.SINGLE;\n            case HtmlConstants.DOUBLE:\n                return STUnderline.DOUBLE;\n            case HtmlConstants.DOTTED:\n                return STUnderline.DOTTED;\n            case HtmlConstants.DASHED:\n                return STUnderline.DASH;\n            case HtmlConstants.WAVY:\n                return STUnderline.WAVE;\n            default:\n                return STUnderline.SINGLE;\n        }\n    }\n\n    public static CTPPr getPPr(CTStyle ctStyle) {\n        return ctStyle.isSetPPr() ? ctStyle.getPPr() : ctStyle.addNewPPr();\n    }\n\n    public static CTPPr getPPr(CTP ctp) {\n        return ctp.isSetPPr() ? ctp.getPPr() : ctp.addNewPPr();\n    }\n\n    public static CTPBdr getPBdr(CTPPr pr) {\n        return pr.isSetPBdr() ? pr.getPBdr() : pr.addNewPBdr();\n    }\n\n    public static CTJc getJc(CTPPr pr) {\n        return pr.isSetJc() ? pr.getJc() : pr.addNewJc();\n    }\n\n    public static CTRPr getRPr(CTR ctr) {\n        return ctr.isSetRPr() ? ctr.getRPr() : ctr.addNewRPr();\n    }\n\n    public static CTTcPr getTcPr(CTTc tc) {\n        return tc.isSetTcPr() ? tc.getTcPr() : tc.addNewTcPr();\n    }\n\n    public static CTTcMar getTcMar(CTTcPr tcPr) {\n        return tcPr.isSetTcMar() ? tcPr.getTcMar() : tcPr.addNewTcMar();\n    }\n\n    public static CTTcMar getTcMar(XWPFTableCell cell) {\n        CTTcPr tcPr = getTcPr(cell.getCTTc());\n        return getTcMar(tcPr);\n    }\n\n    public static CTShd getShd(CTPPr pPr) {\n        return pPr.isSetShd() ? pPr.getShd() : pPr.addNewShd();\n    }\n\n    public static CTInd getInd(CTPPr pPr) {\n        return pPr.isSetInd() ? pPr.getInd() : pPr.addNewInd();\n    }\n\n    public static CTInd getInd(XWPFParagraph paragraph) {\n        CTPPr pPr = getPPr(paragraph.getCTP());\n        return getInd(pPr);\n    }\n\n    public static CTSpacing getSpacing(CTPPr pPr) {\n        return pPr.isSetSpacing() ? pPr.getSpacing() : pPr.addNewSpacing();\n    }\n\n    public static CTSpacing getSpacing(XWPFParagraph paragraph) {\n        CTPPr pPr = getPPr(paragraph.getCTP());\n        return getSpacing(pPr);\n    }\n\n    public static CTColor getColor(CTRPr rPr) {\n        return rPr.isSetColor() ? rPr.getColor() : rPr.addNewColor();\n    }\n\n    public static CTUnderline getUnderline(CTRPr rPr) {\n        return rPr.isSetU() ? rPr.getU() : rPr.addNewU();\n    }\n\n    /**\n     * 获取父容器的可用宽度，以EMU为单位\n     *\n     * @param body 父容器\n     * @return 可用宽度\n     */\n    public static int getAvailableWidthInEMU(IBody body) {\n        if (body.getPartType() == BodyType.DOCUMENT) {\n            XWPFDocument document = (XWPFDocument) body;\n            CTSectPr sectPr = document.getDocument().getBody().getSectPr();\n            int availableWidth = sectPr.getPgSz().getW().intValue()\n                    - sectPr.getPgMar().getLeft().intValue() - sectPr.getPgMar().getRight().intValue();\n            return Units.TwipsToEMU((short) availableWidth);\n\n        } else if (body.getPartType() == BodyType.TABLECELL) {\n            XWPFTableCell tableCell = ((XWPFTableCell) body);\n            CTTblWidth tcW = tableCell.getCTTc().getTcPr().getTcW();\n            if (TableWidthType.DXA.getStWidthType().equals(tcW.getType())) {\n                int availableWidth = tcW.getW().intValue() - TABLE_CELL_MARGIN * 2;\n                return availableWidth > 0 ? Units.TwipsToEMU((short) availableWidth) : 0;\n            } else if (TableWidthType.PCT.getStWidthType().equals(tcW.getType())) {\n                CTTblWidth tblW = tableCell.getTableRow().getTable().getCTTbl().getTblPr().getTblW();\n                if (TableWidthType.DXA.getStWidthType().equals(tblW.getType())) {\n                    int availableWidth = tblW.getW().intValue() * tcW.getW().intValue() / 5000 - TABLE_CELL_MARGIN * 2;\n                    return availableWidth > 0 ? Units.TwipsToEMU((short) availableWidth) : 0;\n                } else if (TableWidthType.NIL.getStWidthType().equals(tblW.getType())) {\n                    return 0;\n                } else {\n                    return Integer.MAX_VALUE;\n                }\n            } else if (TableWidthType.NIL.getStWidthType().equals(tcW.getType())) {\n                return 0;\n            } else {\n                return Integer.MAX_VALUE;\n            }\n\n        } else {\n            throw new UnsupportedOperationException(\"Get bounds of \" + body.getPartType() + \" is not supported yet\");\n        }\n    }\n\n    /**\n     * 应用段落样式\n     *\n     * @param context 渲染上下文\n     * @param paragraph 段落\n     * @param cssStyleDeclaration CSS样式声明\n     */\n    public static void paragraphStyle(HtmlRenderContext context, XWPFParagraph paragraph, CSSStyleDeclarationImpl cssStyleDeclaration) {\n        /* inheritable styles */\n        // alignment\n        ParagraphAlignment align = align(context.getPropertyValue(HtmlConstants.CSS_TEXT_ALIGN));\n        if (align != null) {\n            paragraph.setAlignment(align);\n        }\n\n        if (CSSStyleUtils.isEmpty(cssStyleDeclaration)) {\n            return;\n        }\n\n        // border\n        setBorder(context, paragraph, cssStyleDeclaration);\n\n        // spacing\n        setSpacing(context, paragraph, cssStyleDeclaration);\n\n        // indent\n        setIndentation(context, paragraph, cssStyleDeclaration);\n\n        // background\n        String backgroundColor = cssStyleDeclaration.getBackgroundColor();\n        if (StringUtils.isNotBlank(backgroundColor)) {\n            String color = Colors.fromStyle(backgroundColor, null);\n            if (color != null) {\n                CTPPr pPr = getPPr(paragraph.getCTP());\n                CTShd shd = getShd(pPr);\n                shd.setFill(color);\n            }\n        }\n    }\n\n    /**\n     * 设置段落行距\n     *\n     * @param context 渲染上下文\n     * @param paragraph 段落\n     * @param cssStyleDeclaration CSS样式声明\n     */\n    private static void setSpacing(HtmlRenderContext context, XWPFParagraph paragraph,\n                                   CSSStyleDeclarationImpl cssStyleDeclaration) {\n        // margin-top\n        CSSLength marginTop = CSSLength.of(cssStyleDeclaration.getMarginTop().toLowerCase());\n        if (marginTop.isValid() && !marginTop.isPercent()) {\n            getSpacing(paragraph).setBefore(BigInteger.valueOf(emuToTwips(context.lengthToEMU(marginTop))));\n        }\n\n        // margin-bottom\n        CSSLength marginBottom = CSSLength.of(cssStyleDeclaration.getMarginBottom().toLowerCase());\n        if (marginBottom.isValid() && !marginBottom.isPercent()) {\n            getSpacing(paragraph).setAfter(BigInteger.valueOf(emuToTwips(context.lengthToEMU(marginBottom))));\n        }\n\n        // line-height\n        String lineHeight = context.getPropertyValue(HtmlConstants.CSS_LINE_HEIGHT);\n        if (StringUtils.isNotBlank(lineHeight)) {\n            CSSLength cssLength = CSSLength.of(lineHeight);\n            if (cssLength.isValid()) {\n                if (cssLength.isPercent()) {\n                    CTSpacing spacing = getSpacing(paragraph);\n                    spacing.setLineRule(STLineSpacingRule.AUTO);\n                    spacing.setLine(BigInteger.valueOf(Math.round(cssLength.unitValue() * SPACING_FACTOR)));\n                } else if (cssLength.getValue() > 0) {\n                    CTSpacing spacing = getSpacing(paragraph);\n                    spacing.setLineRule(STLineSpacingRule.EXACT);\n                    spacing.setLine(BigInteger.valueOf(emuToTwips(context.lengthToEMU(cssLength))));\n                }\n            } else if (NumberUtils.isParsable(lineHeight)) {\n                double value = Double.parseDouble(lineHeight);\n                if (value > 0) {\n                    CTSpacing spacing = getSpacing(paragraph);\n                    spacing.setLineRule(STLineSpacingRule.AUTO);\n                    spacing.setLine(BigInteger.valueOf(Math.round(value * SPACING_FACTOR)));\n                }\n            }\n        }\n    }\n\n    /**\n     * 设置段落缩进\n     *\n     * @param context 渲染上下文\n     * @param paragraph 段落\n     * @param cssStyleDeclaration CSS样式声明\n     */\n    private static void setIndentation(HtmlRenderContext context, XWPFParagraph paragraph,\n                                       CSSStyleDeclarationImpl cssStyleDeclaration) {\n        // margin-left\n        CSSLength marginLeft = CSSLength.of(cssStyleDeclaration.getMarginLeft().toLowerCase());\n        if (marginLeft.isValid() && !marginLeft.isPercent()) {\n            getInd(paragraph).setLeft(BigInteger.valueOf(emuToTwips(context.lengthToEMU(marginLeft))));\n        }\n\n        // margin-right\n        CSSLength marginRight = CSSLength.of(cssStyleDeclaration.getMarginRight().toLowerCase());\n        if (marginRight.isValid() && !marginRight.isPercent()) {\n            getInd(paragraph).setRight(BigInteger.valueOf(emuToTwips(context.lengthToEMU(marginRight))));\n        }\n\n        // text-indent\n        String textIndent = context.getPropertyValue(HtmlConstants.CSS_TEXT_INDENT).trim();\n        if (textIndent.isEmpty()) {\n            indent(context, paragraph, textIndent);\n        } else {\n            int nonSpace = -1;\n            boolean indented = false;\n            for (int i = 0; i < textIndent.length(); i++) {\n                char c = textIndent.charAt(i);\n                if (Character.isWhitespace(c)) {\n                    if (nonSpace >= 0) {\n                        indented = indent(context, paragraph, textIndent.substring(nonSpace, i));\n                    }\n                    nonSpace = -1;\n                } else if (nonSpace < 0) {\n                    nonSpace = i;\n                }\n                if (indented) {\n                    break;\n                }\n            }\n            if (nonSpace >= 0) {\n                indent(context, paragraph, textIndent.substring(nonSpace));\n            }\n        }\n    }\n\n    /**\n     * 段落首行缩进\n     *\n     * @param context 渲染上下文\n     * @param paragraph 段落\n     * @param style 缩进样式值\n     * @return 是否进行了缩进\n     */\n    private static boolean indent(HtmlRenderContext context, XWPFParagraph paragraph, String style) {\n        CSSLength cssLength = CSSLength.of(style.toLowerCase());\n        if (cssLength.isValid() && cssLength.getValue() > 0) {\n            CTPPr pPr = getPPr(paragraph.getCTP());\n            CTInd ind = getInd(pPr);\n            double indent;\n            if (cssLength.isPercent()) {\n                indent = context.getAvailableWidthInEMU() * cssLength.unitValue() / CSSLengthUnit.TWIP.absoluteFactor();\n            } else {\n                indent = context.lengthToEMU(cssLength) / CSSLengthUnit.TWIP.absoluteFactor();\n            }\n            ind.setFirstLine(BigInteger.valueOf(Math.round(indent)));\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * 设置段落边框样式\n     *\n     * @param context 上下文\n     * @param xwpfElement 段落\n     * @param cssStyleDeclaration CSS边框样式声明\n     * @param styleProperty CSS边框属性名称\n     * @param widthProperty CSS边框宽度名称\n     * @param colorProperty CSS边框颜色名称\n     * @param getter 获取边框对象的方式\n     * @return 边框是否为none\n     */\n    private static boolean setBorder(HtmlRenderContext context, Object xwpfElement, CSSStyleDeclarationImpl cssStyleDeclaration,\n                                     String styleProperty, String widthProperty, String colorProperty,\n                                     Function<Object, CTBorder> getter) {\n        String borderStyle = cssStyleDeclaration.getPropertyValue(styleProperty);\n        STBorder.Enum style = borderStyle(borderStyle);\n        String borderWidth = cssStyleDeclaration.getPropertyValue(widthProperty);\n        CSSLength width = CSSLength.of(borderWidth);\n        if (style != null && (!width.isValid() || width.getValue() > 0)) {\n            CTBorder border = getter.apply(xwpfElement);\n            border.setVal(style);\n\n            String borderColor = cssStyleDeclaration.getPropertyValue(colorProperty);\n            String color = Colors.TRANSPARENT.equals(borderColor) ? Colors.WHITE : Colors.fromStyle(borderColor);\n            border.setColor(color);\n\n            if (width.isValid() && !width.isPercent()) {\n                long widthValue = (long) context.lengthToEMU(width) * BORDER_WIDTH_PER_PX / Units.EMU_PER_PIXEL;\n                if (widthValue < MIN_BORDER_WIDTH) {\n                    widthValue = MIN_BORDER_WIDTH;\n                } else if (widthValue > MAX_BORDER_WIDTH) {\n                    widthValue = MAX_BORDER_WIDTH;\n                }\n                border.setSz(BigInteger.valueOf(widthValue));\n            } else {\n                border.setSz(BigInteger.valueOf(BORDER_WIDTH_PER_PX));\n            }\n        }\n        return style == STBorder.NONE || style == STBorder.NIL;\n    }\n\n    /**\n     * 边框样式映射\n     *\n     * @param style CSS边框样式值\n     * @return Word边框样式\n     */\n    private static STBorder.Enum borderStyle(String style) {\n        if (StringUtils.isBlank(style)) {\n            return null;\n        }\n\n        switch (style.toLowerCase()) {\n            case HtmlConstants.DOTTED:\n                return STBorder.DOTTED;\n            case HtmlConstants.DASHED:\n                return STBorder.DASHED;\n            case HtmlConstants.SOLID:\n                return STBorder.SINGLE;\n            case HtmlConstants.DOUBLE:\n                return STBorder.DOUBLE;\n            case HtmlConstants.GROOVE:\n            case HtmlConstants.INSET:\n                return STBorder.INSET;\n            case HtmlConstants.RIDGE:\n            case HtmlConstants.OUTSET:\n                return STBorder.OUTSET;\n            case HtmlConstants.NONE:\n                return STBorder.NONE;\n            default:\n                return null;\n        }\n    }\n\n    public static CTTblBorders getTblBorders(CTTbl tbl) {\n        CTTblPr tblPr = getTblPr(tbl);\n        return getTblBorders(tblPr);\n    }\n\n    private static CTBorder getTop(Object e) {\n        if (e instanceof XWPFParagraph) {\n            XWPFParagraph paragraph = (XWPFParagraph) e;\n            return getParagraphTop(paragraph.getCTP());\n        } else if (e instanceof XWPFTable) {\n            XWPFTable table = (XWPFTable) e;\n            return getTableTop(table.getCTTbl());\n        } else if (e instanceof XWPFTableCell) {\n            XWPFTableCell cell = (XWPFTableCell) e;\n            return getTableCellTop(cell.getCTTc());\n        } else {\n            throw new UnsupportedOperationException(\"Can not get top border of \" + e.getClass().getName());\n        }\n    }\n\n    public static CTBorder getParagraphTop(CTP paragraph) {\n        CTPPr pPr = getPPr(paragraph);\n        CTPBdr pBdr = getPBdr(pPr);\n        return pBdr.isSetTop() ? pBdr.getTop() : pBdr.addNewTop();\n    }\n\n    public static CTBorder getTableTop(CTTbl table) {\n        CTTblBorders tblBorders = getTblBorders(table);\n        return tblBorders.isSetTop() ? tblBorders.getTop() : tblBorders.addNewTop();\n    }\n\n    public static CTBorder getTableCellTop(CTTc cell) {\n        CTTcPr tcPr = getTcPr(cell);\n        CTTcBorders tcBorders = getTcBorders(tcPr);\n        return tcBorders.isSetTop() ? tcBorders.getTop() : tcBorders.addNewTop();\n    }\n\n    private static CTBorder getRight(Object e) {\n        if (e instanceof XWPFParagraph) {\n            XWPFParagraph paragraph = (XWPFParagraph) e;\n            return getParagraphRight(paragraph.getCTP());\n        } else if (e instanceof XWPFTable) {\n            XWPFTable table = (XWPFTable) e;\n            return getTableRight(table.getCTTbl());\n        } else if (e instanceof XWPFTableCell) {\n            XWPFTableCell cell = (XWPFTableCell) e;\n            return getTableCellRight(cell.getCTTc());\n        } else {\n            throw new UnsupportedOperationException(\"Can not get right border of \" + e.getClass().getName());\n        }\n    }\n\n    public static CTBorder getParagraphRight(CTP paragraph) {\n        CTPPr pPr = getPPr(paragraph);\n        CTPBdr pBdr = getPBdr(pPr);\n        return pBdr.isSetRight() ? pBdr.getRight() : pBdr.addNewRight();\n    }\n\n    public static CTBorder getTableRight(CTTbl tbl) {\n        CTTblBorders tblBorders = getTblBorders(tbl);\n        return tblBorders.isSetRight() ? tblBorders.getRight() : tblBorders.addNewRight();\n    }\n\n    public static CTBorder getTableCellRight(CTTc cell) {\n        CTTcPr tcPr = getTcPr(cell);\n        CTTcBorders tcBorders = getTcBorders(tcPr);\n        return tcBorders.isSetRight() ? tcBorders.getRight() : tcBorders.addNewRight();\n    }\n\n    private static CTBorder getBottom(Object e) {\n        if (e instanceof XWPFParagraph) {\n            XWPFParagraph paragraph = (XWPFParagraph) e;\n            return getParagraphBottom(paragraph.getCTP());\n        } else if (e instanceof XWPFTable) {\n            XWPFTable table = (XWPFTable) e;\n            return getTableBottom(table.getCTTbl());\n        } else if (e instanceof XWPFTableCell) {\n            XWPFTableCell cell = (XWPFTableCell) e;\n            return getTableCellBottom(cell.getCTTc());\n        } else {\n            throw new UnsupportedOperationException(\"Can not get bottom border of \" + e.getClass().getName());\n        }\n    }\n\n    public static CTBorder getParagraphBottom(CTP paragraph) {\n        CTPPr pPr = getPPr(paragraph);\n        CTPBdr pBdr = getPBdr(pPr);\n        return pBdr.isSetBottom() ? pBdr.getBottom() : pBdr.addNewBottom();\n    }\n\n    public static CTBorder getTableBottom(CTTbl table) {\n        CTTblBorders tblBorders = getTblBorders(table);\n        return tblBorders.isSetBottom() ? tblBorders.getBottom() : tblBorders.addNewBottom();\n    }\n\n    public static CTBorder getTableCellBottom(CTTc cell) {\n        CTTcPr tcPr = getTcPr(cell);\n        CTTcBorders tcBorders = getTcBorders(tcPr);\n        return tcBorders.isSetBottom() ? tcBorders.getBottom() : tcBorders.addNewBottom();\n    }\n\n    private static CTBorder getLeft(Object e) {\n        if (e instanceof XWPFParagraph) {\n            XWPFParagraph paragraph = (XWPFParagraph) e;\n            return getParagraphLeft(paragraph.getCTP());\n        } else if (e instanceof XWPFTable) {\n            XWPFTable table = (XWPFTable) e;\n            return getTableLeft(table.getCTTbl());\n        } else if (e instanceof XWPFTableCell) {\n            XWPFTableCell cell = (XWPFTableCell) e;\n            return getTableCellLeft(cell.getCTTc());\n        } else {\n            throw new UnsupportedOperationException(\"Can not get left border of \" + e.getClass().getName());\n        }\n    }\n\n    public static CTBorder getParagraphLeft(CTP paragraph) {\n        CTPPr pPr = getPPr(paragraph);\n        CTPBdr pBdr = getPBdr(pPr);\n        return pBdr.isSetLeft() ? pBdr.getLeft() : pBdr.addNewLeft();\n    }\n\n    public static CTBorder getTableLeft(CTTbl table) {\n        CTTblBorders tblBorders = getTblBorders(table);\n        return tblBorders.isSetLeft() ? tblBorders.getLeft() : tblBorders.addNewLeft();\n    }\n\n    public static CTBorder getTableCellLeft(CTTc cell) {\n        CTTcPr tcPr = getTcPr(cell);\n        CTTcBorders tcBorders = getTcBorders(tcPr);\n        return tcBorders.isSetLeft() ? tcBorders.getLeft() : tcBorders.addNewLeft();\n    }\n\n    /**\n     * 获取小一号字号\n     *\n     * @param inheritedSizeInHalfPoints 当前字号\n     * @return 字号\n     */\n    public static int smallerFontSizeInHalfPoints(int inheritedSizeInHalfPoints) {\n        for (int i = FONT_SIZE_IN_HALF_POINTS.length - 1; i >= 0; i--) {\n            int s = FONT_SIZE_IN_HALF_POINTS[i];\n            if (s < inheritedSizeInHalfPoints) {\n                return s;\n            }\n        }\n        return FONT_SIZE_IN_HALF_POINTS[0];\n    }\n\n    /**\n     * 获取大一号字号\n     *\n     * @param inheritedSizeInHalfPoints 当前字号\n     * @return 字号\n     */\n    public static int largerFontSizeInHalfPoints(int inheritedSizeInHalfPoints) {\n        for (int s : FONT_SIZE_IN_HALF_POINTS) {\n            if (s > inheritedSizeInHalfPoints) {\n                return s;\n            }\n        }\n        return FONT_SIZE_IN_HALF_POINTS[FONT_SIZE_IN_HALF_POINTS.length - 1];\n    }\n\n    /**\n     * EMU转twip\n     *\n     * @see Units#TwipsToEMU\n     */\n    public static int emuToTwips(int emu) {\n        return (int) (emu * 20L / Units.EMU_PER_POINT);\n    }\n\n    /**\n     * 应用表格样式\n     *\n     * @param context 渲染上下文\n     * @param table 表格\n     * @param cssStyleDeclaration CSS样式声明\n     */\n    public static void tableStyle(HtmlRenderContext context, XWPFTable table, CSSStyleDeclarationImpl cssStyleDeclaration) {\n        if (CSSStyleUtils.isEmpty(cssStyleDeclaration)) {\n            return;\n        }\n\n        // alignment\n        TableRowAlign align = alignTable(cssStyleDeclaration.getPropertyValue(HtmlConstants.CSS_FLOAT));\n        if (align != null) {\n            table.setTableAlignment(align);\n        }\n\n        // border\n        boolean allNone = setBorder(context, table, cssStyleDeclaration);\n        // 如果四边都是none则将单元格间的边框也置为none\n        if (allNone) {\n            CTTblBorders tblBorders = getTblBorders(table.getCTTbl());\n            CTBorder insideH = getTblInsideH(tblBorders);\n            insideH.setVal(STBorder.NONE);\n            CTBorder insideV = getTblInsideV(tblBorders);\n            insideV.setVal(STBorder.NONE);\n        }\n\n        // indent\n        String marginLeft = cssStyleDeclaration.getPropertyValue(HtmlConstants.CSS_MARGIN_LEFT);\n        if (StringUtils.isNotBlank(marginLeft)) {\n            indent(context, table, marginLeft);\n        }\n\n        // background\n        String backgroundColor = cssStyleDeclaration.getBackgroundColor();\n        if (StringUtils.isNotBlank(backgroundColor)) {\n            String color = Colors.fromStyle(backgroundColor, null);\n            if (color != null) {\n                CTTblPr tblPr = getTblPr(table.getCTTbl());\n                CTShd shd = getShd(tblPr);\n                shd.setFill(color);\n            }\n        }\n    }\n\n    public static CTBorder getTblInsideV(CTTblBorders tblBorders) {\n        return tblBorders.isSetInsideV() ? tblBorders.getInsideV() : tblBorders.addNewInsideV();\n    }\n\n    public static CTBorder getTblInsideH(CTTblBorders tblBorders) {\n        return tblBorders.isSetInsideH() ? tblBorders.getInsideH() : tblBorders.addNewInsideH();\n    }\n\n\n    /**\n     * 应用表格样式\n     *\n     * @param context 渲染上下文\n     * @param cell 表格\n     * @param cssStyleDeclaration CSS样式声明\n     */\n    public static void cellStyle(HtmlRenderContext context, XWPFTableCell cell, CSSStyleDeclarationImpl cssStyleDeclaration) {\n        if (CSSStyleUtils.isEmpty(cssStyleDeclaration)) {\n            return;\n        }\n\n        // padding\n        setCellPadding(context, cell, cssStyleDeclaration);\n\n        // alignment\n        XWPFTableCell.XWPFVertAlign align = alignTableCell(cssStyleDeclaration.getVerticalAlign());\n        if (align != null) {\n            cell.setVerticalAlignment(align);\n        }\n\n        // border\n        setBorder(context, cell, cssStyleDeclaration);\n\n        // background\n        String backgroundColor = cssStyleDeclaration.getBackgroundColor();\n        if (StringUtils.isNotBlank(backgroundColor)) {\n            String color = Colors.fromStyle(backgroundColor, null);\n            if (color != null) {\n                CTTcPr tcPr = getTcPr(cell.getCTTc());\n                CTShd shd = getShd(tcPr);\n                shd.setFill(color);\n            }\n        }\n    }\n\n    /**\n     * 设置单元格边距\n     *\n     * @param context 渲染上下文\n     * @param cell 表格\n     * @param cssStyleDeclaration CSS样式声明\n     */\n    private static void setCellPadding(HtmlRenderContext context, XWPFTableCell cell,\n                                       CSSStyleDeclarationImpl cssStyleDeclaration) {\n        // margin-top\n        CSSLength paddingTop = CSSLength.of(cssStyleDeclaration.getPaddingTop().toLowerCase());\n        if (paddingTop.isValid() && !paddingTop.isPercent() && paddingTop.getValue() >= 0) {\n            CTTblWidth top = getTcMar(cell).addNewTop();\n            top.setType(STTblWidth.DXA);\n            top.setW(BigInteger.valueOf(emuToTwips(context.lengthToEMU(paddingTop))));\n        }\n\n        // margin-right\n        CSSLength paddingRight = CSSLength.of(cssStyleDeclaration.getPaddingRight().toLowerCase());\n        if (paddingRight.isValid() && !paddingRight.isPercent() && paddingRight.getValue() >= 0) {\n            CTTblWidth right = getTcMar(cell).addNewRight();\n            right.setType(STTblWidth.DXA);\n            right.setW(BigInteger.valueOf(emuToTwips(context.lengthToEMU(paddingRight))));\n        }\n\n        // margin-bottom\n        CSSLength paddingBottom = CSSLength.of(cssStyleDeclaration.getPaddingBottom().toLowerCase());\n        if (paddingBottom.isValid() && !paddingBottom.isPercent() && paddingBottom.getValue() >= 0) {\n            CTTblWidth bottom = getTcMar(cell).addNewBottom();\n            bottom.setType(STTblWidth.DXA);\n            bottom.setW(BigInteger.valueOf(emuToTwips(context.lengthToEMU(paddingBottom))));\n        }\n\n        // margin-left\n        CSSLength paddingLeft = CSSLength.of(cssStyleDeclaration.getPaddingLeft().toLowerCase());\n        if (paddingLeft.isValid() && !paddingLeft.isPercent() && paddingLeft.getValue() >= 0) {\n            CTTblWidth left = getTcMar(cell).addNewLeft();\n            left.setType(STTblWidth.DXA);\n            left.setW(BigInteger.valueOf(emuToTwips(context.lengthToEMU(paddingLeft))));\n        }\n    }\n\n    /**\n     * 设置上下左右边框样式\n     *\n     * @param xwpfElement 元素\n     * @param cssStyleDeclaration CSS样式声明\n     * @return 是否四边全部为none\n     */\n    public static boolean setBorder(HtmlRenderContext context, Object xwpfElement, CSSStyleDeclarationImpl cssStyleDeclaration) {\n        boolean topNone = setBorder(context, xwpfElement, cssStyleDeclaration, HtmlConstants.CSS_BORDER_TOP_STYLE,\n                HtmlConstants.CSS_BORDER_TOP_WIDTH, HtmlConstants.CSS_BORDER_TOP_COLOR, RenderUtils::getTop);\n        boolean rightNone = setBorder(context, xwpfElement, cssStyleDeclaration, HtmlConstants.CSS_BORDER_RIGHT_STYLE,\n                HtmlConstants.CSS_BORDER_RIGHT_WIDTH, HtmlConstants.CSS_BORDER_RIGHT_COLOR, RenderUtils::getRight);\n        boolean bottomNone = setBorder(context, xwpfElement, cssStyleDeclaration, HtmlConstants.CSS_BORDER_BOTTOM_STYLE,\n                HtmlConstants.CSS_BORDER_BOTTOM_WIDTH, HtmlConstants.CSS_BORDER_BOTTOM_COLOR, RenderUtils::getBottom);\n        boolean leftNone = setBorder(context, xwpfElement, cssStyleDeclaration, HtmlConstants.CSS_BORDER_LEFT_STYLE,\n                HtmlConstants.CSS_BORDER_LEFT_WIDTH, HtmlConstants.CSS_BORDER_LEFT_COLOR, RenderUtils::getLeft);\n        return topNone && rightNone && bottomNone && leftNone;\n    }\n\n    private static boolean indent(HtmlRenderContext context, XWPFTable table, String style) {\n        CSSLength cssLength = CSSLength.of(style.toLowerCase());\n        if (cssLength.isValid() && cssLength.getValue() > 0) {\n            CTTblPr tblPr = getTblPr(table.getCTTbl());\n            CTTblWidth ind = getInd(tblPr);\n            double indent;\n            if (cssLength.isPercent()) {\n                indent = context.getAvailableWidthInEMU() * cssLength.unitValue() / CSSLengthUnit.TWIP.absoluteFactor();\n            } else {\n                indent = context.lengthToEMU(cssLength) / CSSLengthUnit.TWIP.absoluteFactor();\n            }\n            ind.setType(STTblWidth.DXA);\n            ind.setW(BigInteger.valueOf(Math.round(indent)));\n            return true;\n        }\n        return false;\n    }\n\n    public static CTTblWidth getInd(CTTblPr tblPr) {\n        return tblPr.isSetTblInd() ? tblPr.getTblInd() : tblPr.addNewTblInd();\n    }\n\n    public static CTTblBorders getTblBorders(CTTblPr tblPr) {\n        return tblPr.isSetTblBorders() ? tblPr.getTblBorders() : tblPr.addNewTblBorders();\n    }\n\n    public static CTShd getShd(CTTblPr tblPr) {\n        return tblPr.isSetShd() ? tblPr.getShd() : tblPr.addNewShd();\n    }\n\n    public static CTTblPr getTblPr(CTTbl ctTbl) {\n        CTTblPr tblPr = ctTbl.getTblPr();\n        if (tblPr == null) {\n            tblPr = ctTbl.addNewTblPr();\n        }\n        return tblPr;\n    }\n\n    public static CTTcBorders getTcBorders(CTTcPr tcPr) {\n        return tcPr.isSetTcBorders() ? tcPr.getTcBorders() : tcPr.addNewTcBorders();\n    }\n\n    public static CTShd getShd(CTTcPr tcPr) {\n        return tcPr.isSetShd() ? tcPr.getShd() : tcPr.addNewShd();\n    }\n\n    /**\n     * 表格对齐值映射\n     *\n     * @param cssFloat 表格对齐样式值\n     * @return Word表格对齐枚举\n     */\n    public static TableRowAlign alignTable(String cssFloat) {\n        if (StringUtils.isBlank(cssFloat)) {\n            return null;\n        }\n        switch (cssFloat.toLowerCase()) {\n            case HtmlConstants.LEFT:\n                return TableRowAlign.LEFT;\n            case HtmlConstants.RIGHT:\n                return TableRowAlign.RIGHT;\n            default:\n                return null;\n        }\n    }\n\n    /**\n     * 表格单元格垂直对齐值映射\n     *\n     * @param verticalAlign 垂直对齐值\n     * @return Word表格单元格垂直对齐枚举\n     */\n    public static XWPFTableCell.XWPFVertAlign alignTableCell(String verticalAlign) {\n        if (StringUtils.isBlank(verticalAlign)) {\n            return null;\n        }\n        switch (verticalAlign.toLowerCase()) {\n            case HtmlConstants.MIDDLE:\n                return XWPFTableCell.XWPFVertAlign.CENTER;\n            case HtmlConstants.BOTTOM:\n                return XWPFTableCell.XWPFVertAlign.BOTTOM;\n            default:\n                return XWPFTableCell.XWPFVertAlign.TOP;\n        }\n    }\n\n    /**\n     * 嵌入式图片转换为环绕式图片\n     *\n     * @param drawing 绘图容器\n     * @return 环绕式图片\n     */\n    public static CTAnchor inlineToAnchor(CTDrawing drawing) {\n        CTInline ctInline = drawing.getInlineArray(0);\n        CTAnchor ctAnchor = drawing.addNewAnchor();\n        ctAnchor.setDocPr(ctInline.getDocPr());\n        ctAnchor.setExtent(ctInline.getExtent());\n        ctAnchor.setGraphic(ctInline.getGraphic());\n        if (ctInline.isSetCNvGraphicFramePr()) {\n            ctAnchor.setCNvGraphicFramePr(ctInline.getCNvGraphicFramePr());\n        }\n        drawing.removeInline(0);\n\n        ctAnchor.setAllowOverlap(false);\n        ctAnchor.setBehindDoc(false);\n        ctAnchor.setRelativeHeight(0);\n        ctAnchor.setDistL(0);\n        ctAnchor.setDistR(0);\n        ctAnchor.setDistB(0);\n        ctAnchor.setDistT(0);\n        ctAnchor.setLayoutInCell(true);\n        ctAnchor.setLocked(false);\n\n        ctAnchor.setSimplePos2(false);\n        CTPoint2D simplePos = ctAnchor.addNewSimplePos();\n        simplePos.setX(0);\n        simplePos.setY(0);\n\n        return ctAnchor;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/Span.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.util;\n\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\n\n/**\n * 记录表格单元格跨行列的帮助类\n *\n * @author Draco\n * @since 2021-01-26\n */\npublic class Span {\n    private int row;\n    private int column;\n    private boolean enabled;\n    private CSSStyleDeclarationImpl style;\n\n    public Span(int row, int column, boolean enabled, CSSStyleDeclarationImpl style) {\n        this.row = row;\n        this.column = column;\n        this.enabled = enabled;\n        this.style = style;\n    }\n\n    public int getRow() {\n        return this.row;\n    }\n\n    public int getColumn() {\n        return this.column;\n    }\n\n    public boolean isEnabled() {\n        return this.enabled;\n    }\n\n    public void setRow(int row) {\n        this.row = row;\n    }\n\n    public void setColumn(int column) {\n        this.column = column;\n    }\n\n    public void setEnabled(boolean enabled) {\n        this.enabled = enabled;\n    }\n\n    public CSSStyleDeclarationImpl getStyle() {\n        return style;\n    }\n\n    public String toString() {\n        return \"Span(row=\" + this.getRow() + \", column=\" + this.getColumn() + \", enabled=\" + this.isEnabled() + \")\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/SpanWidth.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.util;\n\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * colspan对应的CSS长度值\n *\n * @author Draco\n * @since 2021-10-19\n */\npublic class SpanWidth extends CSSLength {\n    private final int span;\n    private final int column;\n    private final CSSLength[] lengths;\n    private final boolean explicitWidth;\n\n    public SpanWidth(CSSLength length, int column, int span, boolean explicitWidth) {\n        super(length.getValue(), length.getUnit());\n        this.column = column;\n        this.span = span;\n        lengths = new CSSLength[span];\n        this.explicitWidth = explicitWidth;\n    }\n\n    public void setLength(Map<Integer, CSSLength> map) {\n        if (!isValid()) {\n            for (int i = 0; i < span; i++) {\n                map.putIfAbsent(column + i, CSSLength.INVALID);\n            }\n            return;\n        }\n        int invalidCount = 0;\n        boolean percent = isPercent();\n        double total = percent ? getValue() : unitValue();\n        CSSLengthUnit unit = percent ? CSSLengthUnit.PERCENT : CSSLengthUnit.EMU;\n        for (int i = 0; i < span; i++) {\n            Integer index = column + i;\n            CSSLength length = map.getOrDefault(index, CSSLength.INVALID);\n            lengths[i] = length;\n            if (length.isValid()) {\n                if (percent && length.isPercent()) {\n                    total -= length.getValue();\n                } else if (!percent && !length.isPercent()) {\n                    total -= length.unitValue();\n                } else {\n                    lengths[i] = CSSLength.INVALID;\n                    invalidCount++;\n                }\n            } else {\n                invalidCount++;\n            }\n        }\n        if (invalidCount > 0) {\n            final CSSLength length = new CSSLength(total / invalidCount, unit);\n            for (int i = 0; i < span; i++) {\n                if (!lengths[i].isValid()) {\n                    map.compute(column + i, (key, value) -> {\n                        if (value == null || !value.isValid()) {\n                            return length;\n                        } else if (explicitWidth ^ percent) {\n                            return length;\n                        }\n                        return value;\n                    });\n                }\n            }\n        }\n    }\n\n    public int getSpan() {\n        return span;\n    }\n\n    public int getColumn() {\n        return column;\n    }\n\n    public CSSLength[] getLengths() {\n        return lengths;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        if (!super.equals(o)) return false;\n        SpanWidth spanWidth = (SpanWidth) o;\n        return span == spanWidth.span &&\n                column == spanWidth.column &&\n                super.equals(o);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(column, span);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/WhiteSpaceRule.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html.util;\n\nimport org.ddr.poi.html.HtmlConstants;\n\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * https://developer.mozilla.org/zh-CN/docs/Web/CSS/white-space\n *\n * @author Draco\n * @since 2021-07-15\n */\npublic enum WhiteSpaceRule {\n    NORMAL(HtmlConstants.NORMAL, false, false, false),\n    NO_WRAP(HtmlConstants.NO_WRAP, false, false, false),\n    PRE(HtmlConstants.PRE, true, true, true),\n    PRE_WRAP(HtmlConstants.PRE_WRAP, true, true, true),\n    PRE_LINE(HtmlConstants.PRE_LINE, true, false, false),\n    BREAK_SPACES(HtmlConstants.BREAK_SPACES, true, true, true);\n\n    private final String value;\n    private final boolean keepLineBreak;\n    private final boolean keepSpaceAndTab;\n    private final boolean keepTrailingSpace;\n\n    WhiteSpaceRule(String value, boolean keepLineBreak, boolean keepSpaceAndTab, boolean keepTrailingSpace) {\n        this.value = value;\n        this.keepLineBreak = keepLineBreak;\n        this.keepSpaceAndTab = keepSpaceAndTab;\n        this.keepTrailingSpace = keepTrailingSpace;\n    }\n\n    public String getValue() {\n        return value;\n    }\n\n    public boolean isKeepLineBreak() {\n        return keepLineBreak;\n    }\n\n    public boolean isKeepSpaceAndTab() {\n        return keepSpaceAndTab;\n    }\n\n    public boolean isKeepTrailingSpace() {\n        return keepTrailingSpace;\n    }\n\n    public boolean isNormal() {\n        return this == NORMAL || this == NO_WRAP;\n    }\n\n    private static Map<String, WhiteSpaceRule> rules = Arrays.stream(values()).collect(\n            Collectors.toMap(WhiteSpaceRule::getValue, Function.identity())\n    );\n\n    public static WhiteSpaceRule of(String value) {\n        return rules.get(value);\n    }\n\n    public static WhiteSpaceRule of(String value, WhiteSpaceRule defaultRule) {\n        return rules.getOrDefault(value, defaultRule);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/XWPFParagraphRuns.java",
    "content": "package org.ddr.poi.html.util;\n\nimport org.apache.poi.xwpf.usermodel.IRunElement;\nimport org.apache.poi.xwpf.usermodel.XWPFParagraph;\nimport org.apache.poi.xwpf.usermodel.XWPFRun;\n\nimport java.lang.reflect.Field;\nimport java.util.List;\n\n/**\n * Wrapper class for XWPFParagraph runs\n *\n * @author Draco\n * @since 2022-07-05\n */\npublic class XWPFParagraphRuns {\n    private static Field runsField;\n    private static Field irunsField;\n\n    static {\n        try {\n            runsField = XWPFParagraph.class.getDeclaredField(\"runs\");\n            irunsField = XWPFParagraph.class.getDeclaredField(\"iruns\");\n        } catch (NoSuchFieldException ignored) {\n        }\n        runsField.setAccessible(true);\n        irunsField.setAccessible(true);\n    }\n\n    private List<XWPFRun> runs;\n    private List<IRunElement> iruns;\n\n    @SuppressWarnings(\"unchecked\")\n    public XWPFParagraphRuns(XWPFParagraph paragraph) {\n        try {\n            runs = (List<XWPFRun>) runsField.get(paragraph);\n            iruns = (List<IRunElement>) irunsField.get(paragraph);\n        } catch (IllegalAccessException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * Remove run at position without modifying xml\n     *\n     * @param pos index of run\n     */\n    public void remove(int pos) {\n        XWPFRun run = runs.remove(pos);\n        iruns.remove(run);\n    }\n\n    /**\n     * @return runs count\n     */\n    public int runCount() {\n        return runs.size();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/latex/LaTeXRenderPolicy.java",
    "content": "package org.ddr.poi.latex;\n\nimport com.deepoove.poi.policy.AbstractRenderPolicy;\nimport com.deepoove.poi.render.RenderContext;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.poi.xwpf.usermodel.XWPFParagraph;\nimport org.ddr.poi.math.MathRenderConfig;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;\nimport uk.ac.ed.ph.snuggletex.SnuggleSession;\n\n/**\n * LaTeX字符串渲染策略\n *\n * @author Draco\n * @since 2021-04-14\n */\npublic class LaTeXRenderPolicy extends AbstractRenderPolicy<String> {\n    private final MathRenderConfig config;\n    private SnuggleSession session;\n\n    public LaTeXRenderPolicy() {\n        this(new MathRenderConfig());\n    }\n\n    public LaTeXRenderPolicy(MathRenderConfig config) {\n        this.config = config;\n    }\n\n    public MathRenderConfig getConfig() {\n        return config;\n    }\n\n    @Override\n    protected boolean validate(String data) {\n        if (StringUtils.isBlank(data)) {\n            return false;\n        }\n\n        // https://www2.ph.ed.ac.uk/snuggletex/documentation/overview-and-features.html\n        session = LaTeXUtils.createSession();\n        return LaTeXUtils.parse(session, data);\n    }\n\n    @Override\n    public void doRender(RenderContext<String> context) throws Exception {\n        XWPFParagraph paragraph = (XWPFParagraph) context.getRun().getParent();\n        CTR ctr = context.getRun().getCTR();\n        LaTeXUtils.renderTo(paragraph, ctr, session, config);\n    }\n\n    @Override\n    protected void afterRender(RenderContext<String> context) {\n        clearPlaceholder(context, false);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/latex/LaTeXUtils.java",
    "content": "package org.ddr.poi.latex;\n\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.poi.xwpf.usermodel.TableWidthType;\nimport org.apache.poi.xwpf.usermodel.XWPFParagraph;\nimport org.apache.poi.xwpf.usermodel.XWPFTable;\nimport org.apache.poi.xwpf.usermodel.XWPFTableCell;\nimport org.apache.poi.xwpf.usermodel.XWPFTableRow;\nimport org.apache.xmlbeans.XmlCursor;\nimport org.apache.xmlbeans.XmlObject;\nimport org.ddr.poi.html.util.Colors;\nimport org.ddr.poi.math.MathMLUtils;\nimport org.ddr.poi.math.MathRenderConfig;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBookmark;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.w3c.dom.Node;\nimport org.w3c.dom.NodeList;\nimport org.w3c.dom.Text;\nimport uk.ac.ed.ph.snuggletex.InputError;\nimport uk.ac.ed.ph.snuggletex.SnuggleEngine;\nimport uk.ac.ed.ph.snuggletex.SnuggleInput;\nimport uk.ac.ed.ph.snuggletex.SnuggleSession;\nimport uk.ac.ed.ph.snuggletex.definitions.CorePackageDefinitions;\nimport uk.ac.ed.ph.snuggletex.definitions.Globals;\nimport uk.ac.ed.ph.snuggletex.definitions.LaTeXMode;\nimport uk.ac.ed.ph.snuggletex.dombuilding.MathComplexCommandHandler;\nimport uk.ac.ed.ph.snuggletex.internal.util.XMLUtilities;\nimport uk.ac.ed.ph.snuggletex.utilities.DefaultTransformerFactoryChooser;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * LaTeX工具类\n *\n * @author Draco\n * @since 2023-07-17\n */\npublic class LaTeXUtils {\n    private static final Logger log = LoggerFactory.getLogger(LaTeXUtils.class);\n\n    public static final String TAG_MATH = \"math\";\n    public static final String TAG_TAG = \"tag\";\n\n    static final ConcurrentHashMap<String, String> textCircledMap = new ConcurrentHashMap<>();\n\n    /**\n     * 创建Snuggle会话\n     */\n    public static SnuggleSession createSession() {\n        return Initializer.SNUGGLE_ENGINE.createSession();\n    }\n\n    /**\n     * 解析字符串\n     *\n     * @param session Snuggle会话\n     * @param data    LaTeX字符串\n     * @return 是否为有效的内容\n     */\n    public static boolean parse(SnuggleSession session, String data) {\n        SnuggleInput input = new SnuggleInput(data);\n        boolean valid = false;\n        try {\n            valid = session.parseInput(input);\n        } catch (IOException ignored) {\n            // Will never throw an exception since input is raw string\n        }\n        if (CollectionUtils.isNotEmpty(session.getErrors())) {\n            log.warn(\"Invalid LaTex: {}\", data);\n            for (InputError error : session.getErrors()) {\n                log.warn(\"LaTeX parse error: {}\", error);\n            }\n        }\n        return valid;\n    }\n\n    /**\n     * 将LaTeX渲染到段落中\n     *\n     * @param paragraph 段落\n     * @param ctr       目标run，如果总是在末尾渲染可传null\n     * @param session   Snuggle会话\n     * @param config    公式渲染配置\n     */\n    public static void renderTo(XWPFParagraph paragraph, CTR ctr, SnuggleSession session, MathRenderConfig config) {\n        CTR target = ctr;\n        NodeList nodeList = session.buildDOMSubtree();\n        int length = nodeList.getLength();\n        for (int i = 0; i < length; i++) {\n            Node node = nodeList.item(i);\n            if (node instanceof Text) {\n                target = paragraph.getCTP().addNewR();\n                target.addNewT().setStringValue(node.getTextContent());\n            } else if (TAG_MATH.equals(node.getLocalName())) {\n                String math = XMLUtilities.serializeNode(node,\n                        Initializer.SNUGGLE_ENGINE.getDefaultXMLStringOutputOptions());\n\n                MathMLUtils.renderTo(paragraph, target, math, config);\n            } else if (TAG_TAG.equals(node.getLocalName())) {\n                renderTag(paragraph, ctr, node, config);\n            }\n        }\n    }\n\n    private static void renderTag(XWPFParagraph paragraph, CTR ctr, Node node, MathRenderConfig config) {\n        XmlCursor pCursor = paragraph.getCTP().newCursor();\n        pCursor.push();\n\n        XWPFTable xwpfTable = paragraph.getBody().insertNewTbl(pCursor);\n        // 100% width\n        xwpfTable.setWidth(5000);\n        xwpfTable.setWidthType(TableWidthType.PCT);\n        // no borders\n        xwpfTable.setLeftBorder(XWPFTable.XWPFBorderType.NONE, 0, 0, Colors.BLACK);\n        xwpfTable.setTopBorder(XWPFTable.XWPFBorderType.NONE, 0, 0, Colors.BLACK);\n        xwpfTable.setRightBorder(XWPFTable.XWPFBorderType.NONE, 0, 0, Colors.BLACK);\n        xwpfTable.setBottomBorder(XWPFTable.XWPFBorderType.NONE, 0, 0, Colors.BLACK);\n        xwpfTable.setInsideHBorder(XWPFTable.XWPFBorderType.NONE, 0, 0, Colors.BLACK);\n        xwpfTable.setInsideVBorder(XWPFTable.XWPFBorderType.NONE, 0, 0, Colors.BLACK);\n        // access to the first row of the new table which created in a table cell will lead to a weird exception\n        xwpfTable.removeRow(0);\n        XWPFTableRow row = xwpfTable.createRow();\n        XWPFTableCell mathCell = row.createCell();\n        XWPFParagraph mathParagraph = mathCell.getParagraphs().get(0);\n        XmlCursor copyCursor = mathParagraph.getCTP().newCursor();\n        copyCursor.toEndToken();\n\n        pCursor.pop();\n        pCursor.toFirstChild();\n        boolean hasNextSibling = true;\n        while (hasNextSibling) {\n            XmlObject obj = pCursor.getObject();\n            if (obj instanceof CTPPr) {\n                pCursor.copyXml(copyCursor);\n                hasNextSibling = pCursor.toNextSibling();\n            } else if (obj == null || obj instanceof CTBookmark || ctr.equals(obj)) {\n                hasNextSibling = pCursor.toNextSibling();\n            } else {\n                // moveXml附带了toNextSibling的效果\n                hasNextSibling = pCursor.moveXml(copyCursor);\n            }\n        }\n        copyCursor.dispose();\n        pCursor.dispose();\n\n        // render math\n        String math = XMLUtilities.serializeNode(node.getFirstChild(),\n                Initializer.SNUGGLE_ENGINE.getDefaultXMLStringOutputOptions());\n        MathMLUtils.renderTo(mathParagraph, mathParagraph.createRun().getCTR(), math, config);\n\n        // render tag\n        XWPFTableCell tagCell = row.createCell();\n        tagCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);\n        XWPFParagraph tagParagraph = tagCell.getParagraphs().get(0);\n        String tag = XMLUtilities.serializeNode(node.getLastChild(),\n                Initializer.SNUGGLE_ENGINE.getDefaultXMLStringOutputOptions());\n        MathMLUtils.renderTo(tagParagraph, tagParagraph.createRun().getCTR(), tag, config);\n    }\n\n    private static class Initializer {\n        static final SnuggleEngine SNUGGLE_ENGINE = new SnuggleEngine(DefaultTransformerFactoryChooser.getInstance(), null);\n\n        static {\n            CorePackageDefinitions.getPackage().loadMathCharacterAliases(\"math-character-aliases.txt\");\n            CorePackageDefinitions.getPackage().addComplexCommandSameArgMode(\"dfrac\", false, 2, Globals.MATH_MODE_ONLY, new MathComplexCommandHandler(\"mfrac\"), null);\n\n            try (InputStream is = Globals.class.getClassLoader().getResourceAsStream(\"math-character-circled.txt\");\n                 InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);\n                 BufferedReader br = new BufferedReader(isr)) {\n                String line;\n                while ((line = br.readLine()) != null) {\n                    if (line.startsWith(\"#\")) {\n                        continue;\n                    }\n                    line = line.replaceFirst(\"\\\\s+#.+$\", \"\");\n                    String[] fields = line.split(\"->\");\n                    textCircledMap.put(fields[0], fields[1].trim());\n                }\n            } catch (IOException e) {\n                log.warn(\"Failed to load math-character-circled.txt\", e);\n            }\n            CorePackageDefinitions.getPackage().addComplexCommandOneArg(\"textcircled\", false, Globals.ALL_MODES, LaTeXMode.LR, new TextCircledHandler(), null);\n            CorePackageDefinitions.getPackage().addComplexCommandOneArg(\"tag\", false, Globals.ALL_MODES, LaTeXMode.LR, new TagHandler(), null);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/latex/TagHandler.java",
    "content": "package org.ddr.poi.latex;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.w3c.dom.Node;\nimport uk.ac.ed.ph.snuggletex.definitions.CorePackageDefinitions;\nimport uk.ac.ed.ph.snuggletex.definitions.W3CConstants;\nimport uk.ac.ed.ph.snuggletex.dombuilding.CommandHandler;\nimport uk.ac.ed.ph.snuggletex.internal.DOMBuilder;\nimport uk.ac.ed.ph.snuggletex.internal.SnuggleParseException;\nimport uk.ac.ed.ph.snuggletex.tokens.ArgumentContainerToken;\nimport uk.ac.ed.ph.snuggletex.tokens.CommandToken;\nimport uk.ac.ed.ph.snuggletex.tokens.FlowToken;\nimport uk.ac.ed.ph.snuggletex.tokens.MathCharacterToken;\n\nimport java.util.List;\n\npublic class TagHandler implements CommandHandler {\n    @Override\n    public void handleCommand(DOMBuilder builder, Element parentElement, CommandToken token) throws SnuggleParseException {\n        Document document = builder.getDocument();\n        Element table = document.createElementNS(W3CConstants.XHTML_NAMESPACE, LaTeXUtils.TAG_TAG);\n\n        Node math;\n        if (builder.isBuildingMathMLIsland()) {\n            math = parentElement;\n            while (math != null) {\n                if (LaTeXUtils.TAG_MATH.equals(math.getLocalName()) && builder.getDocument().getDocumentElement().equals(math.getParentNode())) {\n                    break;\n                }\n                math = math.getParentNode();\n            }\n        } else {\n            math = parentElement.getLastChild();\n            while (math != null) {\n                if (LaTeXUtils.TAG_MATH.equals(math.getLocalName())) {\n                    break;\n                }\n                math = math.getPreviousSibling();\n            }\n        }\n        if (math != null) {\n            Node parentNode = math.getParentNode();\n            parentNode.removeChild(math);\n            parentNode.appendChild(table);\n            table.appendChild(math);\n\n            ArgumentContainerToken argument = token.getArguments()[0];\n            List<FlowToken> contents = argument.getContents();\n            contents.add(0, new MathCharacterToken(token.getSlice(), CorePackageDefinitions.getPackage().getMathCharacter(\"(\".codePointAt(0))));\n            contents.add(new MathCharacterToken(token.getSlice(), CorePackageDefinitions.getPackage().getMathCharacter(\")\".codePointAt(0))));\n            builder.buildMathElement(table, token, argument, true);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/latex/TextCircledHandler.java",
    "content": "package org.ddr.poi.latex;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.w3c.dom.Element;\nimport uk.ac.ed.ph.snuggletex.dombuilding.CommandHandler;\nimport uk.ac.ed.ph.snuggletex.internal.DOMBuilder;\nimport uk.ac.ed.ph.snuggletex.internal.SnuggleParseException;\nimport uk.ac.ed.ph.snuggletex.tokens.CommandToken;\n\npublic class TextCircledHandler implements CommandHandler {\n\n    private static final Logger log = LoggerFactory.getLogger(TextCircledHandler.class);\n\n    @Override\n    public void handleCommand(DOMBuilder builder, Element parentElement, CommandToken token) throws SnuggleParseException {\n        String s = builder.extractStringValue(token.getArguments()[0]);\n        String replacement = LaTeXUtils.textCircledMap.get(s);\n        if (replacement != null) {\n            if (builder.isBuildingMathMLIsland()) {\n                builder.appendMathMLTextElement(parentElement, \"mi\", replacement, false);\n            } else {\n                builder.appendXHTMLTextElement(parentElement, \"span\", replacement, false);\n            }\n        } else {\n            log.warn(\"Text circled not found: {}\", s);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/math/EmptyEOfNaryDisplayMode.java",
    "content": "package org.ddr.poi.math;\n\n/**\n * 空的N元组显示模式\n *\n * @author Draco\n * @since 2025-06-09 16:44\n */\npublic enum EmptyEOfNaryDisplayMode {\n    /**\n     * 默认：显示输入框\n     */\n    DEFAULT(0x00),\n    /**\n     * 零宽度\n     */\n    ZERO_WIDTH(0x01),\n    /**\n     * 隐藏输入框\n     */\n    HIDDEN(0x10),\n    /**\n     * 零宽度并且隐藏\n     */\n    ZERO_WIDTH_HIDDEN(0x11);\n\n    private final int value;\n\n    EmptyEOfNaryDisplayMode(int value) {\n        this.value = value;\n    }\n\n    public int getValue() {\n        return value;\n    }\n\n    public boolean isZeroWidth() {\n        return (value & ZERO_WIDTH.getValue()) != 0;\n    }\n\n    public boolean isHidden() {\n        return (value & HIDDEN.getValue()) != 0;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/math/MathMLRenderPolicy.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.math;\n\nimport com.deepoove.poi.policy.AbstractRenderPolicy;\nimport com.deepoove.poi.render.RenderContext;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.poi.xwpf.usermodel.XWPFParagraph;\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Element;\n\n/**\n * MathML字符串渲染策略\n *\n * @author Draco\n * @since 2021-04-10 22:51\n */\npublic class MathMLRenderPolicy extends AbstractRenderPolicy<String> {\n    private final MathRenderConfig config;\n    private Element math;\n\n    public MathMLRenderPolicy() {\n        this(new MathRenderConfig());\n    }\n\n    public MathMLRenderPolicy(MathRenderConfig config) {\n        this.config = config;\n    }\n\n    public MathRenderConfig getConfig() {\n        return config;\n    }\n\n    @Override\n    protected boolean validate(String data) {\n        if (StringUtils.isBlank(data)) {\n            return false;\n        }\n        math = Jsoup.parseBodyFragment(data).selectFirst(\"math\");\n        return math != null;\n    }\n\n    @Override\n    public void doRender(RenderContext<String> context) throws Exception {\n        if (!math.hasAttr(\"xmlns\")) {\n            math.attr(\"xmlns\", \"http://www.w3.org/1998/Math/MathML\");\n        }\n        String mathml = math.outerHtml();\n        MathMLUtils.renderTo((XWPFParagraph) context.getRun().getParent(), context.getRun().getCTR(), mathml, config);\n    }\n\n    @Override\n    protected void afterRender(RenderContext<String> context) {\n        clearPlaceholder(context, false);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/math/MathMLUtils.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.math;\n\nimport net.sf.saxon.s9api.Processor;\nimport net.sf.saxon.s9api.SaxonApiException;\nimport net.sf.saxon.s9api.Serializer;\nimport net.sf.saxon.s9api.Xslt30Transformer;\nimport net.sf.saxon.s9api.XsltCompiler;\nimport net.sf.saxon.s9api.XsltExecutable;\nimport org.apache.commons.lang3.StringEscapeUtils;\nimport org.apache.poi.xwpf.usermodel.XWPFParagraph;\nimport org.apache.xmlbeans.XmlCursor;\nimport org.apache.xmlbeans.XmlException;\nimport org.ddr.poi.util.XmlUtils;\nimport org.openxmlformats.schemas.officeDocument.x2006.math.CTOMath;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFonts;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.xml.namespace.QName;\nimport javax.xml.transform.stream.StreamSource;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.StringReader;\nimport java.io.StringWriter;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * MathML工具类\n *\n * @author Draco\n * @since 2021-04-10 22:56\n */\npublic class MathMLUtils {\n    private static final Logger log = LoggerFactory.getLogger(MathMLUtils.class);\n    private static final String MATH_FONT = \"Cambria Math\";\n    public static final String MATH_NS = \"http://schemas.openxmlformats.org/officeDocument/2006/math\";\n    public static final QName OMATH_QNAME = new QName(MATH_NS, \"oMath\");\n    public static final QName NARY_QNAME = new QName(MATH_NS, \"nary\");\n    public static final QName E_QNAME = new QName(MATH_NS, \"e\");\n    public static final QName PHANT_QNAME = new QName(MATH_NS, \"phant\");\n    public static final QName PHANT_PR_QNAME = new QName(MATH_NS, \"phantPr\");\n    public static final QName ZERO_WIDTH_QNAME = new QName(MATH_NS, \"zeroWid\");\n    public static final QName SHOW_QNAME = new QName(MATH_NS, \"show\");\n    public static final QName VALUE_QNAME = new QName(MATH_NS, \"val\");\n\n    private static final Pattern ESCAPED = Pattern.compile(\"&[a-zA-Z]+;\");\n\n    private static final Set<String> PREDEFINED = new HashSet<>(Arrays.asList(\"&amp;\", \"&lt;\", \"&gt;\", \"&quot;\", \"&apos;\"));\n\n    /**\n     * 将MathML渲染到段落中\n     *\n     * @param paragraph 段落\n     * @param ctr 占位符所属run，如果总是在末尾渲染可传null\n     * @param math MathML字符串\n     */\n    public static void renderTo(XWPFParagraph paragraph, CTR ctr, String math, MathRenderConfig config) {\n        if (log.isDebugEnabled()) {\n            log.info(\"Start rendering MathML: {}\", math);\n        }\n        try (StringReader sr = new StringReader(math);\n             StringWriter sw = new StringWriter()) {\n            Serializer out = newSerializer(sw);\n\n            Initializer.TRANSFORMER.transform(new StreamSource(sr), out);\n\n            String omath = sw.toString();\n            if (log.isDebugEnabled()) {\n                log.info(\"Output OMath: {}\", omath);\n            }\n            addMath(paragraph, ctr, omath, config);\n        } catch (IOException | SaxonApiException | XmlException e) {\n            log.warn(\"Failed to render math: {}\", math, e);\n        }\n    }\n\n    /**\n     * 将html实体符号转换为xml形式\n     */\n    public static String normalize(String math) {\n        Matcher matcher = ESCAPED.matcher(math);\n        StringBuffer buffer = new StringBuffer();\n        while (matcher.find()) {\n            String entity = matcher.group();\n            if (PREDEFINED.contains(entity)) {\n                continue;\n            }\n            int codePoint = StringEscapeUtils.unescapeHtml4(entity).codePointAt(0);\n            matcher.appendReplacement(buffer, \"&#\" + codePoint + \";\");\n        }\n        matcher.appendTail(buffer);\n        math = buffer.toString();\n        return math;\n    }\n\n    /**\n     * 用于惰性加载XSL转换器\n     */\n    private static class Initializer {\n        static final Processor PROCESSOR = new Processor(false);\n        static final Xslt30Transformer TRANSFORMER = createTransformer();\n\n        private static Xslt30Transformer createTransformer() {\n            XsltCompiler compiler = PROCESSOR.newXsltCompiler();\n            XsltExecutable stylesheet;\n            try (InputStream inputStream = MathMLUtils.class.getResourceAsStream(\"/MML2OMML.XSL\")) {\n                stylesheet = compiler.compile(new StreamSource(inputStream));\n            } catch (IOException | SaxonApiException e) {\n                throw new IllegalStateException(\"Failed to load MML2OMML.XSL\", e);\n            }\n            return stylesheet.load30();\n        }\n    }\n\n    private static Serializer newSerializer(StringWriter sw) {\n        Serializer out = Initializer.PROCESSOR.newSerializer(sw);\n        out.setOutputProperty(Serializer.Property.METHOD, \"xml\");\n        out.setOutputProperty(Serializer.Property.INDENT, \"no\");\n        out.setOutputProperty(Serializer.Property.OMIT_XML_DECLARATION, \"yes\");\n        return out;\n    }\n\n    /**\n     * 添加公式到Word\n     *\n     * @param paragraph 段落\n     * @param ctr 占位符所属run，如果总是在末尾渲染可传null\n     * @param omath 由mathml转换得到的omath字符串\n     */\n    private static void addMath(XWPFParagraph paragraph, CTR ctr, String omath, MathRenderConfig config) throws XmlException {\n        CTOMath ctoMath = CTOMath.Factory.parse(omath);\n        // 老版本Office可能无法正常显示，强制设置公式字体\n        XmlCursor xmlCursor = ctoMath.newCursor();\n        while (xmlCursor.hasNextToken()) {\n            XmlCursor.TokenType tokenType = xmlCursor.toNextToken();\n            if (tokenType == XmlCursor.TokenType.START) {\n                QName qName = xmlCursor.getName();\n                if (XmlUtils.R_QNAME.equals(qName)) {\n                    CTFonts ctFonts = ((org.openxmlformats.schemas.officeDocument.x2006.math.CTR) xmlCursor.getObject()).addNewRPr2().addNewRFonts();\n                    ctFonts.setAscii(MATH_FONT);\n                    ctFonts.setHAnsi(MATH_FONT);\n                } else if (E_QNAME.equals(qName)) {\n                    if (!xmlCursor.toFirstChild()) {\n                        xmlCursor.push();\n                        xmlCursor.toParent();\n                        QName parentName = xmlCursor.getName();\n                        xmlCursor.pop();\n                        if (NARY_QNAME.equals(parentName)) {\n                            if (config.getEmptyEOfNaryOption() != EmptyEOfNaryDisplayMode.DEFAULT) {\n                                xmlCursor.toNextToken();\n                                xmlCursor.beginElement(PHANT_QNAME);\n                                xmlCursor.beginElement(PHANT_PR_QNAME);\n                                if (config.getEmptyEOfNaryOption().isZeroWidth()) {\n                                    xmlCursor.beginElement(ZERO_WIDTH_QNAME);\n                                    xmlCursor.toParent();\n                                }\n                                if (config.getEmptyEOfNaryOption().isHidden()) {\n                                    xmlCursor.beginElement(SHOW_QNAME);\n                                    xmlCursor.insertAttributeWithValue(VALUE_QNAME, \"off\");\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        xmlCursor.dispose();\n\n        CTP ctp = paragraph.getCTP();\n\n        if (ctr == null) {\n            ctp.addNewOMath();\n            ctp.setOMathArray(ctp.sizeOfOMathArray() - 1, ctoMath.getOMathArray(0));\n            return;\n        }\n\n        insertMathAfterRun(ctp, ctr, ctoMath);\n    }\n\n    private static void insertMathAfterRun(CTP ctp, CTR ctr, CTOMath ctoMath) {\n        XmlCursor xmlCursor;\n        int oMathIndex = 0;\n        boolean foundCTR = false;\n        xmlCursor = ctp.newCursor();\n        while (xmlCursor.hasNextToken()) {\n            XmlCursor.TokenType tokenType = xmlCursor.toNextToken();\n            if (tokenType == XmlCursor.TokenType.START) {\n                if (xmlCursor.getObject() == ctr) {\n                    foundCTR = true;\n                    xmlCursor.toEndToken();\n                    xmlCursor.toNextToken();\n                    xmlCursor.insertElement(OMATH_QNAME);\n                    break;\n                } else if (OMATH_QNAME.equals(xmlCursor.getName())) {\n                    oMathIndex++;\n                }\n            }\n        }\n        xmlCursor.dispose();\n        if (!foundCTR) {\n            throw new IllegalArgumentException(\"The run does not belong to the paragraph\");\n        }\n        ctp.setOMathArray(oMathIndex, ctoMath.getOMathArray(0));\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/math/MathRenderConfig.java",
    "content": "package org.ddr.poi.math;\n\n/**\n * 公式渲染配置\n * @author Draco\n * @since 2025-06-09 16:08\n */\npublic class MathRenderConfig {\n    private EmptyEOfNaryDisplayMode emptyEOfNaryDisplayMode = EmptyEOfNaryDisplayMode.DEFAULT;\n\n    public EmptyEOfNaryDisplayMode getEmptyEOfNaryOption() {\n        return emptyEOfNaryDisplayMode;\n    }\n\n    public void setEmptyEOfNaryOption(EmptyEOfNaryDisplayMode emptyEOfNaryDisplayMode) {\n        this.emptyEOfNaryDisplayMode = emptyEOfNaryDisplayMode;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/util/ByteArrayCopyStream.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.util;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\n\n/**\n * 可以复用输出的二进制数据并转换为输入的流\n *\n * @author Draco\n * @since 2021-02-09\n */\npublic class ByteArrayCopyStream extends ByteArrayOutputStream {\n    public ByteArrayCopyStream() {\n    }\n\n    public ByteArrayCopyStream(int size) {\n        super(size);\n    }\n\n    /**\n     * @return 转换为输入流\n     */\n    public ByteArrayInputStream toInput() {\n        return new ByteArrayInputStream(buf, 0, count);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/util/HttpURLConnectionUtils.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.util;\n\nimport org.apache.commons.lang3.RandomStringUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.HttpsURLConnection;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSession;\nimport javax.net.ssl.SSLSocketFactory;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.X509TrustManager;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.HttpURLConnection;\nimport java.net.ProtocolException;\nimport java.net.URL;\nimport java.security.KeyManagementException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.NoSuchProviderException;\nimport java.security.SecureRandom;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport java.util.Base64;\n\n/**\n * HttpURLConnection工具类，信任所有https地址\n *\n * @author Draco\n * @since 2019-12-12\n */\npublic class HttpURLConnectionUtils {\n\n    public static final byte[] newLineBytes = \"\\r\\n\".getBytes();\n\n    public static class X509TrustAllManager implements X509TrustManager {\n        @Override\n        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {\n        }\n\n        @Override\n        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {\n        }\n\n        @Override\n        public X509Certificate[] getAcceptedIssuers() {\n            return null;\n        }\n    }\n\n    public static class TrustAllHostname implements HostnameVerifier {\n        @Override\n        public boolean verify(String s, SSLSession sslSession) {\n            return true;\n        }\n    }\n\n    /**\n     * 开启http连接，默认未开启doOutput\n     *\n     * @param urlSpec url\n     * @return http连接\n     */\n    public static HttpURLConnection connect(String urlSpec) throws IOException {\n        return connect(urlSpec, null, null);\n    }\n\n    /**\n     * 开启http连接，默认未开启doOutput\n     *\n     * @param urlSpec url\n     * @param user basic auth用户名\n     * @param password basic auth密码\n     * @return http连接\n     */\n    public static HttpURLConnection connect(String urlSpec, String user, String password) throws IOException {\n        if (!StringUtils.startsWith(urlSpec, \"http\")) {\n            throw new IllegalArgumentException(\"Illegal url: \" + urlSpec);\n        }\n\n        URL url = new URL(urlSpec);\n        HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();\n        httpURLConnection.setUseCaches(false);\n\n        boolean isHttps = StringUtils.startsWith(urlSpec, \"https\");\n        if (isHttps) {\n            HttpsURLConnection httpsURLConnection = (HttpsURLConnection) httpURLConnection;\n            httpsURLConnection.setSSLSocketFactory(trustAllSslSocketFactory());\n            httpsURLConnection.setHostnameVerifier(new TrustAllHostname());\n        }\n\n        if (user != null) {\n            if (password == null) {\n                password = \"\";\n            }\n            String credential = user + \":\" + password;\n            String auth = \"Basic \" + Base64.getEncoder().encodeToString(credential.getBytes());\n            httpURLConnection.setRequestProperty(\"Authorization\", auth);\n        }\n\n        return httpURLConnection;\n    }\n\n    public static SSLSocketFactory trustAllSslSocketFactory() {\n        try {\n            TrustManager[] trustManagers = {new X509TrustAllManager()};\n            SSLContext sslContext = SSLContext.getInstance(\"SSL\", \"SunJSSE\");\n            sslContext.init(null, trustManagers, SecureRandom.getInstance(\"SHA1PRNG\"));\n            return sslContext.getSocketFactory();\n        } catch (NoSuchAlgorithmException | NoSuchProviderException | KeyManagementException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 初始化multipart/form-data请求头\n     *\n     * @param connect http连接\n     * @return boundary\n     */\n    public static String initFormData(HttpURLConnection connect) throws ProtocolException {\n        connect.setDoOutput(true);\n        connect.setRequestMethod(\"POST\");\n        String boundary = \"----WebKitFormBoundary\" + RandomStringUtils.randomAlphanumeric(16);\n        connect.setRequestProperty(\"Content-Type\", \"multipart/form-data; boundary=\" + boundary);\n        return boundary;\n    }\n\n    /**\n     * 初始化User-Agent请求头\n     */\n    public static void initUserAgent(HttpURLConnection connect) {\n        connect.setRequestProperty(\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36\");\n    }\n\n    /**\n     * 添加FormData\n     * @param field 字段名\n     * @param value 字段值\n     * @param data 附加数据\n     */\n    public static void addFormData(OutputStream outputStream, byte[] boundaryBytes,\n                                   String field, String value, InputStream data) throws IOException {\n        outputStream.write(boundaryBytes);\n        outputStream.write(newLineBytes);\n\n        outputStream.write(\"Content-Disposition: form-data; name=\\\"\".getBytes());\n        outputStream.write(field.getBytes());\n        outputStream.write(\"\\\"\".getBytes());\n        if (data != null) {\n            outputStream.write(\"; filename=\\\"\".getBytes());\n            outputStream.write(value.getBytes());\n            outputStream.write(\"\\\"\".getBytes());\n            outputStream.write(newLineBytes);\n\n            outputStream.write(\"Content-Type: application/octet-stream\".getBytes());\n            outputStream.write(newLineBytes);\n            outputStream.write(newLineBytes);\n            // data\n            byte[] buffer = new byte[8192];\n            int n;\n            while (-1 != (n = data.read(buffer))) {\n                outputStream.write(buffer, 0, n);\n            }\n        } else {\n            outputStream.write(newLineBytes);\n            outputStream.write(newLineBytes);\n            outputStream.write(value.getBytes());\n        }\n        outputStream.write(newLineBytes);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/ddr/poi/util/XmlUtils.java",
    "content": "/*\n * Copyright 2016 - 2022 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.util;\n\nimport org.apache.xmlbeans.XmlCursor;\nimport org.apache.xmlbeans.XmlObject;\n\nimport javax.xml.namespace.QName;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * @author Draco\n * @since 2022-02-20 19:15\n */\npublic class XmlUtils {\n    public static final String NS_WORDPROCESSINGML = \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\";\n\n    public static final QName P_QNAME = new QName(NS_WORDPROCESSINGML, \"p\");\n    public static final QName PPR_QNAME = new QName(NS_WORDPROCESSINGML, \"pPr\");\n    public static final QName R_QNAME = new QName(NS_WORDPROCESSINGML, \"r\");\n    public static final QName BR_QNAME = new QName(NS_WORDPROCESSINGML, \"br\");\n    public static final QName TBL_QNAME = new QName(NS_WORDPROCESSINGML, \"tbl\");\n    public static final QName HYPERLINK_QNAME = new QName(NS_WORDPROCESSINGML, \"hyperlink\");\n    public static final QName BOOKMARK_START_QNAME = new QName(NS_WORDPROCESSINGML, \"bookmarkStart\");\n    public static final QName BOOKMARK_END_QNAME = new QName(NS_WORDPROCESSINGML, \"bookmarkEnd\");\n\n    public static final Set<QName> INVALID_R_SIBLINGS = new HashSet<>();\n\n    static {\n        INVALID_R_SIBLINGS.add(PPR_QNAME);\n        INVALID_R_SIBLINGS.add(BOOKMARK_START_QNAME);\n        INVALID_R_SIBLINGS.add(BOOKMARK_END_QNAME);\n    }\n\n    /**\n     * 移除xml元素上声明的命名空间\n     *\n     * @param xmlObject xml元素\n     */\n    public static void removeNamespaces(XmlObject xmlObject) {\n        XmlCursor cursor = xmlObject.newCursor();\n        cursor.toNextToken();\n        while (cursor.hasNextToken()) {\n            if (cursor.isNamespace()) {\n                cursor.removeXml();\n            } else {\n                cursor.toNextToken();\n            }\n        }\n        cursor.dispose();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/CustomHtmlTreeBuilder.java",
    "content": "package org.jsoup.parser;\n\nimport org.ddr.poi.html.HtmlConstants;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\n\nimport java.util.List;\n\n/**\n * 自定义html树构建器，对于svg及其内部的标签使用xml解析模式\n *\n * @author Draco\n * @since 2022-04-15\n */\npublic class CustomHtmlTreeBuilder extends HtmlTreeBuilder {\n    @Override\n    void reconstructFormattingElements() {\n        boolean settingsChanged = false;\n        ParseSettings origin = settings;\n        if (isSvgElement()) {\n            settings = ParseSettings.preserveCase;\n            settingsChanged = true;\n        }\n        super.reconstructFormattingElements();\n        if (settingsChanged) {\n            settings = origin;\n        }\n    }\n\n    @Override\n    Element insert(Token.StartTag startTag) {\n        boolean settingsChanged = false;\n        ParseSettings origin = settings;\n        if (isSvgElement()) {\n            settings = ParseSettings.preserveCase;\n            settingsChanged = true;\n        }\n        Element element = super.insert(startTag);\n        if (settingsChanged) {\n            settings = origin;\n        }\n        return element;\n    }\n\n    @Override\n    public List<Node> parseFragment(String inputFragment, Element context, String baseUri, Parser parser) {\n        return super.parseFragment(inputFragment, context, baseUri, parser);\n    }\n\n    private boolean isSvgElement() {\n        if (currentToken.isStartTag() && HtmlConstants.TAG_SVG.equals(currentToken.asStartTag().normalName)) {\n            return true;\n        }\n        return getFromStack(HtmlConstants.TAG_SVG) != null;\n    }\n}\n"
  },
  {
    "path": "src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi",
    "content": "org.ddr.image.avif.AvifImageReaderSpi\norg.ddr.image.heif.HeifImageReaderSpi"
  },
  {
    "path": "src/main/resources/MML2OMML.XSL",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n                xmlns:mml=\"http://www.w3.org/1998/Math/MathML\"\n                xmlns:m=\"http://schemas.openxmlformats.org/officeDocument/2006/math\">\n    <xsl:output method=\"xml\" encoding=\"UTF-8\"/>\n\n\n    <xsl:variable name=\"StrUCAlphabet\">ABCDEFGHIJKLMNOPQRSTUVWXYZ</xsl:variable>\n    <xsl:variable name=\"StrLCAlphabet\">abcdefghijklmnopqrstuvwxyz</xsl:variable>\n\n    <!-- %%Template: match *\n\n\t\tThe catch all template, just passes through\n\t-->\n    <xsl:template match=\"*\">\n        <xsl:apply-templates select=\"*\"/>\n    </xsl:template>\n\n    <!-- %%Template: match *\n\n\t\tAnother catch all template, just passes through\n\t-->\n    <xsl:template match=\"/\">\n        <m:oMath>\n            <xsl:apply-templates select=\"*\"/>\n        </m:oMath>\n    </xsl:template>\n\n    <!-- %%Template: SReplace\n\n\t\tReplace all occurences of sOrig in sInput with sReplacement\n\t\tand return the resulting string. -->\n    <xsl:template name=\"SReplace\">\n        <xsl:param name=\"sInput\"/>\n        <xsl:param name=\"sOrig\"/>\n        <xsl:param name=\"sReplacement\"/>\n\n        <xsl:choose>\n            <xsl:when test=\"not(contains($sInput, $sOrig))\">\n                <xsl:value-of select=\"$sInput\"/>\n            </xsl:when>\n            <xsl:otherwise>\n                <xsl:variable name=\"sBefore\" select=\"substring-before($sInput, $sOrig)\"/>\n                <xsl:variable name=\"sAfter\" select=\"substring-after($sInput, $sOrig)\"/>\n                <xsl:variable name=\"sAfterProcessed\">\n                    <xsl:call-template name=\"SReplace\">\n                        <xsl:with-param name=\"sInput\" select=\"$sAfter\"/>\n                        <xsl:with-param name=\"sOrig\" select=\"$sOrig\"/>\n                        <xsl:with-param name=\"sReplacement\" select=\"$sReplacement\"/>\n                    </xsl:call-template>\n                </xsl:variable>\n\n                <xsl:value-of select=\"concat($sBefore, concat($sReplacement, $sAfterProcessed))\"/>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- %%Template: OutputText\n\n\t\tPost processing on the string given and otherwise do\n\t\ta xsl:value-of on it -->\n    <xsl:template name=\"OutputText\">\n        <xsl:param name=\"sInput\"/>\n\n        <!-- Add local variable as you add new post processing tasks -->\n\n        <!-- 1. Remove any unwanted characters -->\n        <xsl:variable name=\"sCharStrip\">\n            <xsl:value-of select=\"translate($sInput, '&#x2062;&#x200B;', '')\"/>\n        </xsl:variable>\n\n        <!-- 2. Replace any characters as needed -->\n        <!--\tReplace &#x2A75; <-> ==\t\t\t -->\n        <xsl:variable name=\"sCharReplace\">\n            <xsl:call-template name=\"SReplace\">\n                <xsl:with-param name=\"sInput\" select=\"$sCharStrip\"/>\n                <xsl:with-param name=\"sOrig\" select=\"'&#x2A75;'\"/>\n                <xsl:with-param name=\"sReplacement\" select=\"'=='\"/>\n            </xsl:call-template>\n        </xsl:variable>\n\n        <!-- Replace &#xa0; (non-breaking space) with ' ' -->\n        <xsl:variable name=\"sCharReplaceFinal\" select=\"translate($sCharReplace, '&#xa0;', ' ')\"/>\n\n        <!-- Finally, return the last value -->\n        <xsl:value-of select=\"$sCharReplaceFinal\"/>\n    </xsl:template>\n\n\n    <!-- Template that determines whether or the given node\n\t     ndCur is a token element that doesn't have an mglyph as\n\t\t\t a child.\n\t-->\n    <xsl:template name=\"FNonGlyphToken\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n        <xsl:choose>\n            <xsl:when test=\"$ndCur/self::mml:mi[not(child::mml:mglyph)] |\n\t                     $ndCur/self::mml:mn[not(child::mml:mglyph)] |\n\t                     $ndCur/self::mml:mo[not(child::mml:mglyph)] |\n\t                     $ndCur/self::mml:ms[not(child::mml:mglyph)] |\n                       $ndCur/self::mml:mtext[not(child::mml:mglyph)]\">1</xsl:when>\n            <xsl:otherwise>0</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n\n    <!-- Template used to determine if the current token element (ndCur) is the beginning of a run.\n\t\t\t A token element is the beginning of if:\n\n\t\t\t the count of preceding elements is 0\n\t\t\t or\n\t\t\t the directory preceding element is not a non-glyph token.\n\t-->\n    <xsl:template name=\"FStartOfRun\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n        <xsl:variable name=\"fPrecSibNonGlyphToken\">\n            <xsl:call-template name=\"FNonGlyphToken\">\n                <xsl:with-param name=\"ndCur\" select=\"$ndCur/preceding-sibling::*[1]\"/>\n            </xsl:call-template>\n        </xsl:variable>\n        <xsl:choose>\n            <xsl:when test=\"count($ndCur/preceding-sibling::*)=0\n\t\t\t\t\t\t\t\t\t\t\tor $fPrecSibNonGlyphToken=0\">1</xsl:when>\n            <xsl:otherwise>0</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- Template that determines if ndCur is the argument of an nary expression.\n\n\t\t\t ndCur is the argument of an nary expression if:\n\n\t\t\t 1.  The preceding sibling is one of the following:  munder, mover, msub, msup, munder, msubsup, munderover\n\t\t\t and\n\t\t\t 2.  The preceding sibling's child is an nary char as specified by the template \"isNary\"\n\t-->\n    <xsl:template name=\"FIsNaryArgument\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n\n        <xsl:variable name=\"fNary\">\n            <xsl:call-template name=\"isNary\">\n                <xsl:with-param name=\"ndCur\" select=\"$ndCur/preceding-sibling::*[1]/child::*[1]\"/>\n            </xsl:call-template>\n        </xsl:variable>\n        <xsl:choose>\n            <xsl:when test=\"preceding-sibling::*[1][self::mml:munder or self::mml:mover or self::mml:munderover or\n                                                    self::mml:msub or self::mml:msup or self::mml:msubsup]\n\t\t\t\t\t\t\t      and $fNary='true'\">1</xsl:when>\n            <xsl:otherwise>0</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- %%Template: mml:mrow | mml:mstyle\n\n\t\t if this row is the next sibling of an n-ary (i.e. any of\n         mover, munder, munderover, msupsub, msup, or msub with\n         the base being an n-ary operator) then ignore this. Otherwise\n         pass through -->\n    <xsl:template match=\"mml:mrow|mml:mstyle\">\n        <xsl:variable name=\"fNaryArgument\">\n            <xsl:call-template name=\"FIsNaryArgument\">\n                <xsl:with-param name=\"ndCur\" select=\".\"/>\n            </xsl:call-template>\n        </xsl:variable>\n        <xsl:if test=\"$fNaryArgument=0\">\n            <xsl:variable name=\"fLinearFrac\">\n                <xsl:call-template name=\"FLinearFrac\">\n                    <xsl:with-param name=\"ndCur\" select=\".\"/>\n                </xsl:call-template>\n            </xsl:variable>\n            <xsl:choose>\n                <xsl:when test=\"$fLinearFrac=1\">\n                    <xsl:call-template name=\"MakeLinearFraction\">\n                        <xsl:with-param name=\"ndCur\" select=\".\"/>\n                    </xsl:call-template>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:variable name=\"fFunc\">\n                        <xsl:call-template name=\"FIsFunc\">\n                            <xsl:with-param name=\"ndCur\" select=\".\"/>\n                        </xsl:call-template>\n                    </xsl:variable>\n                    <xsl:choose>\n                        <xsl:when test=\"$fFunc=1\">\n                            <xsl:call-template name=\"WriteFunc\">\n                                <xsl:with-param name=\"ndCur\" select=\".\"/>\n                            </xsl:call-template>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:apply-templates select=\"*\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:if>\n    </xsl:template>\n    <xsl:template match=\"mml:mi[not(child::mml:mglyph)] |\n\t                     mml:mn[not(child::mml:mglyph)] |\n\t                     mml:mo[not(child::mml:mglyph)] |\n\t                     mml:ms[not(child::mml:mglyph)] |\n                       mml:mtext[not(child::mml:mglyph)]\">\n\n        <!-- tokens with mglyphs as children are tranformed\n\t\t\t in a different manner than \"normal\" token elements.\n\t\t\t Where normal token elements are token elements that\n\t\t\t contain only text -->\n        <xsl:variable name=\"fStartOfRun\">\n            <xsl:call-template name=\"FStartOfRun\">\n                <xsl:with-param name=\"ndCur\" select=\".\"/>\n            </xsl:call-template>\n        </xsl:variable>\n\n        <!--In MathML, successive characters that are all part of one string are sometimes listed as separate\n\t\t\ttags based on their type (identifier (mi), name (mn), operator (mo), quoted (ms), literal text (mtext)),\n\t\t\twhere said tags act to link one another into one logical run.  In order to wrap the text of successive mi's,\n\t\t\tmn's, and mo's into one m:t, we need to denote where a run begins.  The beginning of a run is the first mi, mn,\n\t\t\tor mo whose immediately preceding sibling either doesn't exist or is something other than a \"normal\" mi, mn, mo,\n\t\t\tms, or mtext tag-->\n\n        <!-- If this mi/mo/mn/ms . . . is part the numerator or denominator of a linear fraction, then don't collect. -->\n        <xsl:variable name=\"fLinearFracParent\">\n            <xsl:call-template name=\"FLinearFrac\">\n                <xsl:with-param name=\"ndCur\" select=\"parent::*\"/>\n            </xsl:call-template>\n        </xsl:variable>\n        <!-- If this mi/mo/mn/ms . . . is part of the name of a function, then don't collect. -->\n        <xsl:variable name=\"fFunctionName\">\n            <xsl:call-template name=\"FIsFunc\">\n                <xsl:with-param name=\"ndCur\" select=\"parent::*\"/>\n            </xsl:call-template>\n        </xsl:variable>\n        <xsl:variable name=\"fShouldCollect\"\n                      select=\"($fLinearFracParent=0 and $fFunctionName=0) and (parent::mml:mrow or parent::mml:mstyle or\n\t\t\t\t\tparent::mml:msqrt or parent::mml:menclose or\n\t\t\t\t\tparent::mml:math or parent::mml:mphantom or\n\t\t\t\t\tparent::mml:mtd or parent::mml:maction)\"/>\n\n        <!--In MathML, the meaning of the different parts that make up mathematical structures, such as a fraction\n\t\t\thaving a numerator and a denominator, is determined by the relative order of those different parts.\n\t\t\tFor instance, In a fraction, the numerator is the first child and the denominator is the second child.\n\t\t\tTo allow for more complex structures, MathML allows one to link a group of mi, mn, and mo's together\n\t\t\tusing the mrow, or mstyle tags.  The mi, mn, and mo's found within any of the above tags are considered\n\t\t\tone run.  Therefore, if the parent of any mi, mn, or mo is found to be an mrow or mstyle, then the contiguous\n\t\t\tmi, mn, and mo's will be considered one run.-->\n        <xsl:choose>\n            <xsl:when test=\"$fShouldCollect\">\n                <xsl:choose>\n                    <xsl:when test=\"$fStartOfRun=1\">\n                        <!--If this is the beginning of the run, pass all run attributes to CreateRunWithSameProp.-->\n                        <xsl:call-template name=\"CreateRunWithSameProp\">\n                            <xsl:with-param name=\"mathbackground\">\n                                <!-- Look for the unqualified mathml attribute mathbackground.\n\t\t\t\t\t\t\t\t\t\t Fall back to the qualified mathml attribute if necessary.\n\t\t\t\t\t\t\t\t\t\t This priority of unqualified over qualified will be\n\t\t\t\t\t\t\t\t\t\t followed throughout this xslt. -->\n                                <xsl:choose>\n                                    <xsl:when test=\"@mathbackground\">\n                                        <xsl:value-of select=\"@mathbackground\"/>\n                                    </xsl:when>\n                                    <xsl:otherwise>\n                                        <xsl:value-of select=\"@mml:mathbackground\"/>\n                                    </xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:with-param>\n                            <xsl:with-param name=\"mathcolor\">\n                                <xsl:choose>\n                                    <xsl:when test=\"@mathcolor\">\n                                        <xsl:value-of select=\"@mathcolor\"/>\n                                    </xsl:when>\n                                    <xsl:otherwise>\n                                        <xsl:value-of select=\"@mml:mathcolor\"/>\n                                    </xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:with-param>\n                            <xsl:with-param name=\"mathvariant\">\n                                <xsl:choose>\n                                    <xsl:when test=\"@mathvariant\">\n                                        <xsl:value-of select=\"@mathvariant\"/>\n                                    </xsl:when>\n                                    <xsl:otherwise>\n                                        <xsl:value-of select=\"@mml:mathvariant\"/>\n                                    </xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:with-param>\n                            <xsl:with-param name=\"color\">\n                                <xsl:choose>\n                                    <xsl:when test=\"@color\">\n                                        <xsl:value-of select=\"@color\"/>\n                                    </xsl:when>\n                                    <xsl:otherwise>\n                                        <xsl:value-of select=\"@mml:color\"/>\n                                    </xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:with-param>\n                            <xsl:with-param name=\"font-family\">\n                                <xsl:choose>\n                                    <xsl:when test=\"@font-family\">\n                                        <xsl:value-of select=\"@font-family\"/>\n                                    </xsl:when>\n                                    <xsl:otherwise>\n                                        <xsl:value-of select=\"@mml:font-family\"/>\n                                    </xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:with-param>\n                            <xsl:with-param name=\"fontsize\">\n                                <xsl:choose>\n                                    <xsl:when test=\"@fontsize\">\n                                        <xsl:value-of select=\"@fontsize\"/>\n                                    </xsl:when>\n                                    <xsl:otherwise>\n                                        <xsl:value-of select=\"@mml:fontsize\"/>\n                                    </xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:with-param>\n                            <xsl:with-param name=\"fontstyle\">\n                                <xsl:choose>\n                                    <xsl:when test=\"@fontstyle\">\n                                        <xsl:value-of select=\"@fontstyle\"/>\n                                    </xsl:when>\n                                    <xsl:otherwise>\n                                        <xsl:value-of select=\"@mml:fontstyle\"/>\n                                    </xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:with-param>\n                            <xsl:with-param name=\"fontweight\">\n                                <xsl:choose>\n                                    <xsl:when test=\"@fontweight\">\n                                        <xsl:value-of select=\"@fontweight\"/>\n                                    </xsl:when>\n                                    <xsl:otherwise>\n                                        <xsl:value-of select=\"@mml:fontweight\"/>\n                                    </xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:with-param>\n                            <xsl:with-param name=\"mathsize\">\n                                <xsl:choose>\n                                    <xsl:when test=\"@mathsize\">\n                                        <xsl:value-of select=\"@mathsize\"/>\n                                    </xsl:when>\n                                    <xsl:otherwise>\n                                        <xsl:value-of select=\"@mml:mathsize\"/>\n                                    </xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:with-param>\n                            <xsl:with-param name=\"ndTokenFirst\" select=\".\"/>\n                        </xsl:call-template>\n                    </xsl:when>\n                </xsl:choose>\n            </xsl:when>\n            <xsl:otherwise>\n                <!--Only one element will be part of run-->\n                <xsl:element name=\"m:r\">\n                    <!--Create Run Properties based on current node's attributes-->\n                    <xsl:call-template name=\"CreateRunProp\">\n                        <xsl:with-param name=\"mathvariant\">\n                            <xsl:choose>\n                                <xsl:when test=\"@mathvariant\">\n                                    <xsl:value-of select=\"@mathvariant\"/>\n                                </xsl:when>\n                                <xsl:otherwise>\n                                    <xsl:value-of select=\"@mml:mathvariant\"/>\n                                </xsl:otherwise>\n                            </xsl:choose>\n                        </xsl:with-param>\n                        <xsl:with-param name=\"fontstyle\">\n                            <xsl:choose>\n                                <xsl:when test=\"@fontstyle\">\n                                    <xsl:value-of select=\"@fontstyle\"/>\n                                </xsl:when>\n                                <xsl:otherwise>\n                                    <xsl:value-of select=\"@mml:fontstyle\"/>\n                                </xsl:otherwise>\n                            </xsl:choose>\n                        </xsl:with-param>\n                        <xsl:with-param name=\"fontweight\">\n                            <xsl:choose>\n                                <xsl:when test=\"@fontweight\">\n                                    <xsl:value-of select=\"@fontweight\"/>\n                                </xsl:when>\n                                <xsl:otherwise>\n                                    <xsl:value-of select=\"@mml:fontweight\"/>\n                                </xsl:otherwise>\n                            </xsl:choose>\n                        </xsl:with-param>\n                        <xsl:with-param name=\"mathcolor\">\n                            <xsl:choose>\n                                <xsl:when test=\"@mathcolor\">\n                                    <xsl:value-of select=\"@mathcolor\"/>\n                                </xsl:when>\n                                <xsl:otherwise>\n                                    <xsl:value-of select=\"@mml:mathcolor\"/>\n                                </xsl:otherwise>\n                            </xsl:choose>\n                        </xsl:with-param>\n                        <xsl:with-param name=\"mathsize\">\n                            <xsl:choose>\n                                <xsl:when test=\"@mathsize\">\n                                    <xsl:value-of select=\"@mathsize\"/>\n                                </xsl:when>\n                                <xsl:otherwise>\n                                    <xsl:value-of select=\"@mml:mathsize\"/>\n                                </xsl:otherwise>\n                            </xsl:choose>\n                        </xsl:with-param>\n                        <xsl:with-param name=\"color\">\n                            <xsl:choose>\n                                <xsl:when test=\"@color\">\n                                    <xsl:value-of select=\"@color\"/>\n                                </xsl:when>\n                                <xsl:otherwise>\n                                    <xsl:value-of select=\"@mml:color\"/>\n                                </xsl:otherwise>\n                            </xsl:choose>\n                        </xsl:with-param>\n                        <xsl:with-param name=\"fontsize\">\n                            <xsl:choose>\n                                <xsl:when test=\"@fontsize\">\n                                    <xsl:value-of select=\"@fontsize\"/>\n                                </xsl:when>\n                                <xsl:otherwise>\n                                    <xsl:value-of select=\"@mml:fontsize\"/>\n                                </xsl:otherwise>\n                            </xsl:choose>\n                        </xsl:with-param>\n                        <xsl:with-param name=\"ndCur\" select=\".\"/>\n                        <xsl:with-param name=\"fNor\">\n                            <xsl:call-template name=\"FNor\">\n                                <xsl:with-param name=\"ndCur\" select=\".\"/>\n                            </xsl:call-template>\n                        </xsl:with-param>\n                    </xsl:call-template>\n                    <xsl:element name=\"m:t\">\n                        <xsl:call-template name=\"OutputText\">\n                            <xsl:with-param name=\"sInput\" select=\"normalize-space(.)\"/>\n                        </xsl:call-template>\n                    </xsl:element>\n                </xsl:element>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- %%Template: CreateRunWithSameProp\n\t-->\n    <xsl:template name=\"CreateRunWithSameProp\">\n        <xsl:param name=\"mathbackground\"/>\n        <xsl:param name=\"mathcolor\"/>\n        <xsl:param name=\"mathvariant\"/>\n        <xsl:param name=\"color\"/>\n        <xsl:param name=\"font-family\"/>\n        <xsl:param name=\"fontsize\"/>\n        <xsl:param name=\"fontstyle\"/>\n        <xsl:param name=\"fontweight\"/>\n        <xsl:param name=\"mathsize\"/>\n        <xsl:param name=\"ndTokenFirst\"/>\n\n        <!--Given mathcolor, color, mstyle's (ancestor) color, and precedence of\n\t\t\tsaid attributes, determine the actual color of the current run-->\n        <xsl:variable name=\"sColorPropCur\">\n            <xsl:choose>\n                <xsl:when test=\"$mathcolor!=''\">\n                    <xsl:value-of select=\"$mathcolor\"/>\n                </xsl:when>\n                <xsl:when test=\"$color!=''\">\n                    <xsl:value-of select=\"$color\"/>\n                </xsl:when>\n                <xsl:when test=\"$ndTokenFirst/ancestor::mml:mstyle[@color][1]/@color!=''\">\n                    <xsl:value-of select=\"$ndTokenFirst/ancestor::mml:mstyle[@color][1]/@color\"/>\n                </xsl:when>\n                <xsl:when test=\"$ndTokenFirst/ancestor::mml:mstyle[@mml:color][1]/@mml:color!=''\">\n                    <xsl:value-of select=\"$ndTokenFirst/ancestor::mml:mstyle[@color][1]/@mml:color\"/>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:value-of select=\"''\"/>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n\n        <!--Given mathsize, and fontsize and precedence of said attributes,\n\t\t\tdetermine the actual font size of the current run-->\n        <xsl:variable name=\"sSzCur\">\n            <xsl:choose>\n                <xsl:when test=\"$mathsize!=''\">\n                    <xsl:value-of select=\"$mathsize\"/>\n                </xsl:when>\n                <xsl:when test=\"$fontsize!=''\">\n                    <xsl:value-of select=\"$fontsize\"/>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:value-of select=\"''\"/>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n\n        <!--Given mathvariant, fontstyle, and fontweight, and precedence of\n\t\t\tthe attributes, determine the actual font of the current run-->\n        <xsl:variable name=\"sFontCur\">\n            <xsl:call-template name=\"GetFontCur\">\n                <xsl:with-param name=\"mathvariant\" select=\"$mathvariant\"/>\n                <xsl:with-param name=\"fontstyle\" select=\"$fontstyle\"/>\n                <xsl:with-param name=\"fontweight\" select=\"$fontweight\"/>\n                <xsl:with-param name=\"ndCur\" select=\"$ndTokenFirst\"/>\n            </xsl:call-template>\n        </xsl:variable>\n\n        <!-- The omml equivalent structure for mml:mtext is an omml run with the run property m:nor (normal) set.\n         Therefore, we can only collect mtexts with  other mtext elements.  Suppose the $ndTokenFirst is an\n         mml:mtext, then if any of its following siblings are to be grouped, they must also be mml:text elements.\n         The inverse is also true, suppose the $ndTokenFirst isn't an mml:mtext, then if any of its following siblings\n         are to be grouped with $ndTokenFirst, they can't be mml:mtext elements-->\n        <xsl:variable name=\"fNdTokenFirstIsMText\">\n            <xsl:choose>\n                <xsl:when test=\"$ndTokenFirst/self::mml:mtext\">1</xsl:when>\n                <xsl:otherwise>0</xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n\n        <!--In order to determine the length of the run, we will find the number of nodes before the inital node in the run and\n\t\t\tthe number of nodes before the first node that DOES NOT belong to the current run.  The number of nodes that will\n\t\t\tbe printed is One Less than the difference between the latter and the former-->\n\n        <!--Find index of current node-->\n        <xsl:variable name=\"nndBeforeFirst\" select=\"count($ndTokenFirst/preceding-sibling::*)\"/>\n\n        <!--Find index of next change in run properties.\n\n\t\t    The basic idea is that we want to find the position of the last node in the longest\n\t\t\t\tsequence of nodes, starting from ndTokenFirst, that can be grouped into a run.  For\n\t\t\t\texample, nodes A and B can be grouped together into the same run iff they have the same\n\t\t\t\tprops.\n\n\t\t\t\tTo accomplish this grouping, we want to find the next sibling to ndTokenFirst that shouldn't be\n\t\t\t\tincluded in the run of text.  We do this by counting the number of elements that precede the first\n\t\t\t\tsuch element that doesn't belong.  The xpath that accomplishes this is below.\n\n\t\t\t\t\t\tCount the number of siblings the precede the first element after ndTokenFirst that shouldn't belong.\n\t\t\t\t\t\tcount($ndTokenFirst/following-sibling::*[ . . . ][1]/preceding-sibling::*)\n\n\t\t\t\tNow, the hard part to this is what is represented by the '. . .' above.  This conditional expression is\n\t\t\t\tdefining what elements *don't* belong to the current run.  The conditions are as follows:\n\n\t\t\t\tThe element is not a token element (mi, mn, mo, ms, or mtext)\n\n\t\t\t\tor\n\n\t\t\t\tThe token element contains a glyph child (this is handled separately).\n\n\t\t\t\tor\n\n\t\t\t\tThe token is an mtext and the run didn't start with an mtext, or the token isn't an mtext and the run started\n\t\t\t\twith an mtext.  We do this check because mtext transforms into an omml m:nor property, and thus, these mtext\n\t\t\t\ttoken elements need to be grouped separately from other token elements.\n\n\t\t\t\t// We do an or not( . . . ), because it was easier to define what token elements match than how they don't match.\n\t\t\t\t// Thus, this inner '. . .' defines how token attributes equate to one another.  We add the 'not' outside of to accomplish\n\t\t\t\t// the goal of the outer '. . .', which is the find the next element that *doesn't* match.\n\t\t\t\tor not(\n\t\t\t\t   The background colors match.\n\n\t\t\t\t\t and\n\n\t\t\t\t\t\t\tThe current font (sFontCur) matches the mathvariant\n\n\t\t\t\t\t\t\tor\n\n\t\t\t\t\t\t\tsFontCur is normal and matches the current font characteristics\n\n\t\t\t\t\t\t\tor\n\n\t\t\t\t\t\t\tsFontCur is italic and matches the current font characteristics\n\n\t\t\t\t\t\t\tor\n\n\t\t\t\t\t\t\t. . .\n\n\t\t\t\t\t and\n\n\t\t\t\t\t The font family matches the current font family.\n\t\t\t\t\t ) // end of not().-->\n        <xsl:variable name=\"nndBeforeLim\" select=\"count($ndTokenFirst/following-sibling::*\n\t\t\t\t\t[(not(self::mml:mi) and not(self::mml:mn) and not(self::mml:mo) and not(self::mml:ms) and not(self::mml:mtext))\n\t\t\t\t\tor\n\t\t\t\t\t(self::mml:mi[child::mml:mglyph] or self::mml:mn[child::mml:mglyph] or self::mml:mo[child::mml:mglyph] or self::mml:ms[child::mml:mglyph] or self::mml:mtext[child::mml:mglyph])\n\t\t\t\t\tor\n\t\t\t\t\t(($fNdTokenFirstIsMText=1 and not(self::mml:mtext)) or ($fNdTokenFirstIsMText=0 and self::mml:mtext))\n\t\t\t\t\tor\n\t\t\t\t\tnot(\n\t\t\t\t\t\t((($sFontCur=@mathvariant or $sFontCur=@mml:mathvariant)\n\t\t\t\t\t\t\tor\n\t\t\t\t\t\t\t($sFontCur='normal'\n\t\t\t\t\t\t\t and ((@mathvariant='normal' or @mml:mathvariant='normal')\n\t\t\t\t\t\t\t\t\t  or (((not(@mathvariant) or @mathvariant='') and (not(@mml:mathvariant) or @mml:mathvariant=''))\n\t\t\t\t\t\t\t\t\t\t\t  and (\n\t\t\t\t\t\t\t\t\t           ((@fontstyle='normal' or @mml:fontstyle='normal') and (not(@fontweight='bold') and not(@mml:fontweight='bold')))\n\t\t\t\t\t\t\t\t\t           or (self::mml:mi and string-length(normalize-space(.)) &gt; 1)\n\t\t\t\t\t\t\t\t\t           or (self::mml:mn and string(number(self::mml:mn/text()))='NaN')\n\t\t\t\t\t\t\t\t\t          )\n\t\t\t\t\t\t\t\t\t     )\n\t\t\t\t\t\t\t\t\t )\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tor\n\t\t\t\t\t\t\t($sFontCur='italic'\n\t\t\t\t\t\t\t and ((@mathvariant='italic' or @mml:mathvariant='italic')\n\t\t\t\t\t\t\t\t\t  or (((not(@mathvariant) or @mathvariant='') and (not(@mml:mathvariant) or @mml:mathvariant=''))\n\t\t\t\t\t\t\t\t\t\t\t\tand (\n\t\t\t\t\t\t\t\t\t           ((@fontstyle='italic' or @mml:fontstyle='italic') and (not(@fontweight='bold') and not(@mml:fontweight='bold')))\n\t\t\t\t\t\t\t\t\t\t         or\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t((self::mml:mn and string(number(self::mml:mn/text()))!='NaN')\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t or self::mml:mo\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t or (self::mml:mi and string-length(normalize-space(.)) &lt;= 1)\n\t\t\t\t\t\t\t\t\t            )\n\t\t\t\t\t\t\t\t\t          )\n\t\t\t\t\t\t\t\t\t     )\n\t\t\t\t\t\t\t\t\t )\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tor\n\t\t\t\t\t\t\t($sFontCur='bold'\n\t\t\t\t\t\t\t and ((@mathvariant='bold' or @mml:mathvariant='bold')\n\t\t\t\t\t\t\t\t\t  or (((not(@mathvariant) or @mathvariant='') and (not(@mml:mathvariant) or @mml:mathvariant=''))\n\t\t\t\t\t\t\t\t\t      and (\n\t\t\t\t\t\t\t\t\t           ((@fontweight='bold' or @mml:fontweight='bold')\n\t\t\t\t\t\t\t\t\t           and ((@fontstyle='normal' or @mml:fontstyle='normal') or (self::mml:mi and string-length(normalize-space(.)) &lt;= 1))\n\t\t\t\t\t\t\t\t\t          )\n\t\t\t\t\t\t\t\t\t     )\n\t\t\t\t\t\t\t\t\t   )\n\t\t\t\t\t\t\t\t\t )\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t  or\n\t\t\t\t\t\t\t(($sFontCur='bi' or $sFontCur='bold-italic')\n\t\t\t\t\t\t\t and (\n\t\t\t\t\t\t\t\t\t  (@mathvariant='bold-italic' or @mml:mathvariant='bold-italic')\n\t\t\t\t\t\t\t\t\t  or (((not(@mathvariant) or @mathvariant='') and (not(@mml:mathvariant) or @mml:mathvariant=''))\n\t\t\t\t\t\t\t\t\t\t\t\tand (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t ((@fontweight='bold' or @mml:fontweight='bold') and (@fontstyle='italic' or @mml:fontstyle='italic'))\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t or ((@fontweight='bold' or @mml:fontweight='bold')\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  \t and (self::mml:mn\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t \t\t  or self::mml:mo\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tor (self::mml:mi and string-length(normalize-space(.)) &lt;= 1)))\n\t\t\t\t\t\t\t\t\t          )\n\t\t\t\t\t\t\t\t\t     )\n\t\t\t\t\t\t\t\t\t )\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tor\n              (($sFontCur=''\n                 and (\n                     ((not(@mathvariant) or @mathvariant='')\n                        and (not(@mml:mathvariant) or @mml:mathvariant='')\n                        and (not(@fontstyle) or @fontstyle='')\n                        and (not(@mml:fontstyle) or @mml:fontstyle='')\n                        and (not(@fontweight)or @fontweight='')\n                        and (not(@mml:fontweight) or @mml:fontweight='')\n\t\t              )\n                      or\n                        (@mathvariant='italic' or @mml:mathvariant='italic')\n                      or (\n                           ((not(@mathvariant) or @mathvariant='') and (not(@mml:mathvariant) or @mml:mathvariant=''))\n                               and (\n\t                                 (((@fontweight='normal' or @mml:fontweight='normal')\n\t\t                                and (@fontstyle='italic' or @mml:fontstyle='italic'))\n\t                                 )\n\t                                 or\n\t\t                                ((not(@fontweight) or @fontweight='') and (not(@mml:fontweight) or @mml:fontweight=''))\n\t\t\t                                and (@fontstyle='italic' or @mml:fontstyle='italic')\n\t                                 or\n\t\t                                ((not(@fontweight) or @fontweight='') and (not(@mml:fontweight) or @mml:fontweight=''))\n\t\t\t                                and (not(@fontstyle) or @fontstyle='')\n\t\t\t                                and (not(@mml:fontstyle) or @mml:fontstyle=''))\n                            )\n\t\t              )\n\n              ))\n\t\t\t\t\t\t\tor\n              ($sFontCur='normal'\n               and ((self::mml:mi\n                     and (not(@mathvariant) or @mathvariant='')\n\t                 and (not(@mml:mathvariant) or @mml:mathvariant)\n\t                 and (not(@fontstyle) or @fontstyle='')\n\t                 and (not(@mml:fontstyle) or @mml:fontstyle='')\n\t                 and (not(@fontweight) or @fontweight='')\n\t                 and (not(@mml:fontweight) or @mml:fontweight='')\n\t                 and (string-length(normalize-space(.)) &gt; 1)\n\t                 )\n\t                or ((self::mml:ms or self::mml:mtext)\n\t\t                and (not(@mathvariant) or @mathvariant='')\n\t\t                and (not(@mml:mathvariant) or @mml:mathvariant)\n\t\t                and (not(@fontstyle) or @fontstyle)\n\t\t                and (not(@fontstyle) or @fontstyle='')\n\t\t                and (not(@fontweight) or @fontweight)\n\t\t                and (not(@mml:fontweight) or @mml:fontweight='')\n\t\t                )\n\t                )\n              )\n\t\t\t\t\t\t)\n\t\t\t\t\t\tand\n            (($font-family = @font-family or $font-family = @mml:font-family)\n              or (($font-family='' or not($font-family))\n\t              and (not(@font-family) or @font-family='')\n\t              and (not(@mml:font-family) or @mml:font-family='')\n\t             )\n            )\n\t\t\t\t\t))\n\t\t\t\t\t][1]/preceding-sibling::*)\"/>\n\n        <xsl:variable name=\"cndRun\" select=\"$nndBeforeLim - $nndBeforeFirst\"/>\n\n        <!--Contiguous groups of like-property mi, mn, and mo's are separated by non- mi, mn, mo tags, or mi,mn, or mo\n\t\t\ttags with different properties.  nndBeforeLim is the number of nodes before the next tag which separates contiguous\n\t\t\tgroups of like-property mi, mn, and mo's.  Knowing this delimiting tag allows for the aggregation of the correct\n\t\t\tnumber of mi, mn, and mo tags.-->\n        <xsl:element name=\"m:r\">\n\n            <!--The beginning and ending of the current run has been established. Now we should open a run element-->\n            <xsl:choose>\n\n                <!--If cndRun > 0, then there is a following diffrent prop, or non- Token,\n\t\t\t\t\t\talthough there may or may not have been a preceding different prop, or non-\n\t\t\t\t\t\tToken-->\n                <xsl:when test=\"$cndRun &gt; 0\">\n                    <xsl:call-template name=\"CreateRunProp\">\n                        <xsl:with-param name=\"mathvariant\" select=\"$mathvariant\"/>\n                        <xsl:with-param name=\"fontstyle\" select=\"$fontstyle\"/>\n                        <xsl:with-param name=\"fontweight\" select=\"$fontweight\"/>\n                        <xsl:with-param name=\"mathcolor\" select=\"$mathcolor\"/>\n                        <xsl:with-param name=\"mathsize\" select=\"$mathsize\"/>\n                        <xsl:with-param name=\"color\" select=\"$color\"/>\n                        <xsl:with-param name=\"fontsize\" select=\"$fontsize\"/>\n                        <xsl:with-param name=\"ndCur\" select=\"$ndTokenFirst\"/>\n                        <xsl:with-param name=\"fNor\">\n                            <xsl:call-template name=\"FNor\">\n                                <xsl:with-param name=\"ndCur\" select=\"$ndTokenFirst\"/>\n                            </xsl:call-template>\n                        </xsl:with-param>\n                    </xsl:call-template>\n                    <xsl:element name=\"m:t\">\n                        <xsl:call-template name=\"OutputText\">\n                            <xsl:with-param name=\"sInput\">\n                                <xsl:choose>\n                                    <xsl:when\n                                            test=\"namespace-uri($ndTokenFirst) = 'http://www.w3.org/1998/Math/MathML' and local-name($ndTokenFirst) = 'ms'\">\n                                        <xsl:call-template name=\"OutputMs\">\n                                            <xsl:with-param name=\"msCur\" select=\"$ndTokenFirst\"/>\n                                        </xsl:call-template>\n                                    </xsl:when>\n                                    <xsl:otherwise>\n                                        <xsl:value-of select=\"normalize-space($ndTokenFirst)\"/>\n                                    </xsl:otherwise>\n                                </xsl:choose>\n                                <xsl:for-each select=\"$ndTokenFirst/following-sibling::*[position() &lt; $cndRun]\">\n                                    <xsl:choose>\n                                        <xsl:when test=\"namespace-uri(.) = 'http://www.w3.org/1998/Math/MathML' and\n\t\t\t\t\t\t\t\t\t\t\t\t\tlocal-name(.) = 'ms'\">\n                                            <xsl:call-template name=\"OutputMs\">\n                                                <xsl:with-param name=\"msCur\" select=\".\"/>\n                                            </xsl:call-template>\n                                        </xsl:when>\n                                        <xsl:otherwise>\n                                            <xsl:value-of select=\"normalize-space(.)\"/>\n                                        </xsl:otherwise>\n                                    </xsl:choose>\n                                </xsl:for-each>\n                            </xsl:with-param>\n                        </xsl:call-template>\n                    </xsl:element>\n                </xsl:when>\n                <xsl:otherwise>\n\n                    <!--if cndRun lt;= 0, then iNextNonToken = 0,\n\t\t\t\t\t\tand iPrecNonToken gt;= 0.  In either case, b/c there\n\t\t\t\t\t\tis no next different property or non-Token\n\t\t\t\t\t\t(which is implied by the nndBeforeLast being equal to 0)\n\t\t\t\t\t\tyou can put all the remaining mi, mn, and mo's into one\n\t\t\t\t\t\tgroup.-->\n                    <xsl:call-template name=\"CreateRunProp\">\n                        <xsl:with-param name=\"mathvariant\" select=\"$mathvariant\"/>\n                        <xsl:with-param name=\"fontstyle\" select=\"$fontstyle\"/>\n                        <xsl:with-param name=\"fontweight\" select=\"$fontweight\"/>\n                        <xsl:with-param name=\"mathcolor\" select=\"$mathcolor\"/>\n                        <xsl:with-param name=\"mathsize\" select=\"$mathsize\"/>\n                        <xsl:with-param name=\"color\" select=\"$color\"/>\n                        <xsl:with-param name=\"fontsize\" select=\"$fontsize\"/>\n                        <xsl:with-param name=\"ndCur\" select=\"$ndTokenFirst\"/>\n                        <xsl:with-param name=\"fNor\">\n                            <xsl:call-template name=\"FNor\">\n                                <xsl:with-param name=\"ndCur\" select=\"$ndTokenFirst\"/>\n                            </xsl:call-template>\n                        </xsl:with-param>\n                    </xsl:call-template>\n                    <xsl:element name=\"m:t\">\n\n                        <!--Create the Run, first output current, then in a\n\t\t\t\t\t\t\tfor-each, because all the following siblings are\n\t\t\t\t\t\t\tmn, mi, and mo's that conform to the run's properties,\n\t\t\t\t\t\t\tgroup them together-->\n                        <xsl:call-template name=\"OutputText\">\n                            <xsl:with-param name=\"sInput\">\n                                <xsl:choose>\n                                    <xsl:when test=\"namespace-uri($ndTokenFirst) = 'http://www.w3.org/1998/Math/MathML' and\n\t\t\t\t\t\t\t\t\t\t\t\t\tlocal-name($ndTokenFirst) = 'ms'\">\n                                        <xsl:call-template name=\"OutputMs\">\n                                            <xsl:with-param name=\"msCur\" select=\"$ndTokenFirst\"/>\n                                        </xsl:call-template>\n                                    </xsl:when>\n                                    <xsl:otherwise>\n                                        <xsl:value-of select=\"normalize-space($ndTokenFirst)\"/>\n                                    </xsl:otherwise>\n                                </xsl:choose>\n                                <xsl:for-each\n                                        select=\"$ndTokenFirst/following-sibling::*[self::mml:mi or self::mml:mn or self::mml:mo or self::mml:ms or self::mml:mtext]\">\n                                    <xsl:choose>\n                                        <xsl:when test=\"namespace-uri(.) = 'http://www.w3.org/1998/Math/MathML' and\n\t\t\t\t\t\t\t\t\t\t\t\t\tlocal-name(.) = 'ms'\">\n                                            <xsl:call-template name=\"OutputMs\">\n                                                <xsl:with-param name=\"msCur\" select=\".\"/>\n                                            </xsl:call-template>\n                                        </xsl:when>\n                                        <xsl:otherwise>\n                                            <xsl:value-of select=\"normalize-space(.)\"/>\n                                        </xsl:otherwise>\n                                    </xsl:choose>\n                                </xsl:for-each>\n                            </xsl:with-param>\n                        </xsl:call-template>\n                    </xsl:element>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:element>\n\n        <!--The run was terminated by an mi, mn, mo, ms, or mtext with different properties,\n\t\t\t\ttherefore, call-template CreateRunWithSameProp, using cndRun+1 node as new start node-->\n        <xsl:if test=\"$nndBeforeLim!=0\n           and ($ndTokenFirst/following-sibling::*[$cndRun]/self::mml:mi or\n\t\t\t\t\t      $ndTokenFirst/following-sibling::*[$cndRun]/self::mml:mn or\n\t\t\t\t\t      $ndTokenFirst/following-sibling::*[$cndRun]/self::mml:mo or\n\t\t\t\t\t      $ndTokenFirst/following-sibling::*[$cndRun]/self::mml:ms or\n                $ndTokenFirst/following-sibling::*[$cndRun]/self::mml:mtext)\n            and (count($ndTokenFirst/following-sibling::*[$cndRun]/mml:mglyph) = 0)\">\n            <xsl:call-template name=\"CreateRunWithSameProp\">\n                <xsl:with-param name=\"mathbackground\">\n                    <xsl:choose>\n                        <xsl:when test=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mathbackground\">\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mathbackground\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mml:mathbackground\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n                <xsl:with-param name=\"mathcolor\">\n                    <xsl:choose>\n                        <xsl:when test=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mathcolor\">\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mathcolor\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mml:mathcolor\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n                <xsl:with-param name=\"mathvariant\">\n                    <xsl:choose>\n                        <xsl:when test=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mathvariant\">\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mathvariant\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mml:mathvariant\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n                <xsl:with-param name=\"color\">\n                    <xsl:choose>\n                        <xsl:when test=\"$ndTokenFirst/following-sibling::*[$cndRun]/@color\">\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@color\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mml:color\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n                <xsl:with-param name=\"font-family\">\n                    <xsl:choose>\n                        <xsl:when test=\"$ndTokenFirst/following-sibling::*[$cndRun]/@font-family\">\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@font-family\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mml:font-family\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n                <xsl:with-param name=\"fontsize\">\n                    <xsl:choose>\n                        <xsl:when test=\"$ndTokenFirst/following-sibling::*[$cndRun]/@fontsize\">\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@fontsize\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mml:fontsize\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n                <xsl:with-param name=\"fontstyle\">\n                    <xsl:choose>\n                        <xsl:when test=\"$ndTokenFirst/following-sibling::*[$cndRun]/@fontstyle\">\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@fontstyle\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mml:fontstyle\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n                <xsl:with-param name=\"fontweight\">\n                    <xsl:choose>\n                        <xsl:when test=\"$ndTokenFirst/following-sibling::*[$cndRun]/@fontweight\">\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@fontweight\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mml:fontweight\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n                <xsl:with-param name=\"mathsize\">\n                    <xsl:choose>\n                        <xsl:when test=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mathsize\">\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mathsize\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"$ndTokenFirst/following-sibling::*[$cndRun]/@mml:mathsize\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n                <xsl:with-param name=\"ndTokenFirst\" select=\"$ndTokenFirst/following-sibling::*[$cndRun]\"/>\n            </xsl:call-template>\n        </xsl:if>\n    </xsl:template>\n\n    <!-- %%Template: FNor\n\t\t\t\t Given the context of ndCur, determine if ndCur should be omml's normal style.\n\t-->\n    <xsl:template name=\"FNor\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n        <xsl:choose>\n            <!-- Is the current node an mml:mtext, or if this is an mglyph whose parent is\n             an mml:mtext. -->\n            <xsl:when test=\"$ndCur/self::mml:mtext or ($ndCur/self::mml:mglyph and parent::mml:mtext)\">1</xsl:when>\n            <xsl:otherwise>0</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n\n    <!-- %%Template: CreateRunProp\n\t-->\n    <xsl:template name=\"CreateRunProp\">\n        <xsl:param name=\"mathbackground\"/>\n        <xsl:param name=\"mathcolor\"/>\n        <xsl:param name=\"mathvariant\"/>\n        <xsl:param name=\"color\"/>\n        <xsl:param name=\"font-family\"/>\n        <xsl:param name=\"fontsize\"/>\n        <xsl:param name=\"fontstyle\"/>\n        <xsl:param name=\"fontweight\"/>\n        <xsl:param name=\"mathsize\"/>\n        <xsl:param name=\"ndCur\"/>\n        <xsl:param name=\"fontfamily\"/>\n        <xsl:param name=\"fNor\"/>\n        <xsl:variable name=\"mstyleColor\">\n            <xsl:if test=\"not(not($ndCur))\">\n                <xsl:choose>\n                    <xsl:when test=\"$ndCur/ancestor::mml:mstyle[@color][1]/@color\">\n                        <xsl:value-of select=\"$ndCur/ancestor::mml:mstyle[@color][1]/@color\"/>\n                    </xsl:when>\n                    <xsl:otherwise>\n                        <xsl:value-of select=\"$ndCur/ancestor::mml:mstyle[@color][1]/@mml:color\"/>\n                    </xsl:otherwise>\n                </xsl:choose>\n            </xsl:if>\n        </xsl:variable>\n        <xsl:call-template name=\"CreateMathRPR\">\n            <xsl:with-param name=\"mathvariant\" select=\"$mathvariant\"/>\n            <xsl:with-param name=\"fontstyle\" select=\"$fontstyle\"/>\n            <xsl:with-param name=\"fontweight\" select=\"$fontweight\"/>\n            <xsl:with-param name=\"ndCur\" select=\"$ndCur\"/>\n            <xsl:with-param name=\"fNor\" select=\"$fNor\"/>\n        </xsl:call-template>\n    </xsl:template>\n\n    <!-- %%Template: CreateMathRPR\n\t-->\n    <xsl:template name=\"CreateMathRPR\">\n        <xsl:param name=\"mathvariant\"/>\n        <xsl:param name=\"fontstyle\"/>\n        <xsl:param name=\"fontweight\"/>\n        <xsl:param name=\"ndCur\"/>\n        <xsl:param name=\"fNor\"/>\n        <xsl:variable name=\"sFontCur\">\n            <xsl:call-template name=\"GetFontCur\">\n                <xsl:with-param name=\"mathvariant\" select=\"$mathvariant\"/>\n                <xsl:with-param name=\"fontstyle\" select=\"$fontstyle\"/>\n                <xsl:with-param name=\"fontweight\" select=\"$fontweight\"/>\n                <xsl:with-param name=\"ndCur\" select=\"$ndCur\"/>\n            </xsl:call-template>\n        </xsl:variable>\n        <xsl:if test=\"$fNor=1 or ($sFontCur!='italic' and $sFontCur!='')\">\n            <xsl:element name=\"m:rPr\">\n                <xsl:if test=\"$fNor=1\">\n                    <m:nor/>\n                </xsl:if>\n                <xsl:call-template name=\"CreateMathScrStyProp\">\n                    <xsl:with-param name=\"font\" select=\"$sFontCur\"/>\n                    <xsl:with-param name=\"fNor\" select=\"$fNor\"/>\n                </xsl:call-template>\n            </xsl:element>\n        </xsl:if>\n    </xsl:template>\n\n    <!-- %%Template: GetFontCur\n\t-->\n    <xsl:template name=\"GetFontCur\">\n        <xsl:param name=\"ndCur\"/>\n        <xsl:param name=\"mathvariant\"/>\n        <xsl:param name=\"fontstyle\"/>\n        <xsl:param name=\"fontweight\"/>\n        <xsl:choose>\n            <xsl:when test=\"$mathvariant!=''\">\n                <xsl:value-of select=\"$mathvariant\"/>\n            </xsl:when>\n            <xsl:when test=\"not($ndCur)\">\n                <xsl:value-of select=\"'italic'\"/>\n            </xsl:when>\n            <xsl:when test=\"$ndCur/self::mml:mi and (string-length(normalize-space($ndCur)) &lt;= 1)\n\t\t\t\t\t\t\t\t      or $ndCur/self::mml:mn and string(number($ndCur/text()))!='NaN'\n\t\t\t\t\t\t\t\t      or $ndCur/self::mml:mo\">\n\n                <!-- The default for the above three cases is fontstyle=italic fontweight=normal.-->\n                <xsl:choose>\n                    <xsl:when test=\"$fontstyle='normal' and $fontweight='bold'\">\n                        <!-- In omml, a sty of 'b' (which is what bold is translated into)\n\t\t\t\t\t\t     implies a normal fontstyle -->\n                        <xsl:value-of select=\"'bold'\"/>\n                    </xsl:when>\n                    <xsl:when test=\"$fontstyle='normal'\">\n                        <xsl:value-of select=\"'normal'\"/>\n                    </xsl:when>\n                    <xsl:when test=\"$fontweight='bold'\">\n                        <xsl:value-of select=\"'bi'\"/>\n                    </xsl:when>\n                    <xsl:otherwise>\n                        <xsl:value-of select=\"'italic'\"/>\n                    </xsl:otherwise>\n                </xsl:choose>\n            </xsl:when>\n            <xsl:otherwise>\n                <!--Default is fontweight = 'normal' and fontstyle='normal'-->\n                <xsl:choose>\n                    <xsl:when test=\"$fontstyle='italic' and $fontweight='bold'\">\n                        <xsl:value-of select=\"'bi'\"/>\n                    </xsl:when>\n                    <xsl:when test=\"$fontstyle='italic'\">\n                        <xsl:value-of select=\"'italic'\"/>\n                    </xsl:when>\n                    <xsl:when test=\"$fontweight='bold'\">\n                        <xsl:value-of select=\"'bold'\"/>\n                    </xsl:when>\n                    <xsl:otherwise>\n                        <xsl:value-of select=\"'normal'\"/>\n                    </xsl:otherwise>\n                </xsl:choose>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n\n    <!-- %%Template: CreateMathScrStyProp\n\t-->\n    <xsl:template name=\"CreateMathScrStyProp\">\n        <xsl:param name=\"font\"/>\n        <xsl:param name=\"fNor\" select=\"0\"/>\n        <xsl:choose>\n            <xsl:when test=\"$font='normal' and $fNor=0\">\n                <xsl:element name=\"m:sty\">\n                    <xsl:attribute name=\"m:val\">p</xsl:attribute>\n                </xsl:element>\n            </xsl:when>\n            <xsl:when test=\"$font='bold'\">\n                <xsl:element name=\"m:sty\">\n                    <xsl:attribute name=\"m:val\">b</xsl:attribute>\n                </xsl:element>\n            </xsl:when>\n            <xsl:when test=\"$font='italic'\">\n            </xsl:when>\n            <xsl:when test=\"$font='script'\">\n                <xsl:element name=\"m:scr\">\n                    <xsl:attribute name=\"m:val\">script</xsl:attribute>\n                </xsl:element>\n            </xsl:when>\n            <xsl:when test=\"$font='bold-script'\">\n                <xsl:element name=\"m:scr\">\n                    <xsl:attribute name=\"m:val\">script</xsl:attribute>\n                </xsl:element>\n                <xsl:element name=\"m:sty\">\n                    <xsl:attribute name=\"m:val\">b</xsl:attribute>\n                </xsl:element>\n            </xsl:when>\n            <xsl:when test=\"$font='double-struck'\">\n                <xsl:element name=\"m:scr\">\n                    <xsl:attribute name=\"m:val\">double-struck</xsl:attribute>\n                </xsl:element>\n                <xsl:element name=\"m:sty\">\n                    <xsl:attribute name=\"m:val\">p</xsl:attribute>\n                </xsl:element>\n            </xsl:when>\n            <xsl:when test=\"$font='fraktur'\">\n                <xsl:element name=\"m:scr\">\n                    <xsl:attribute name=\"m:val\">fraktur</xsl:attribute>\n                </xsl:element>\n                <xsl:element name=\"m:sty\">\n                    <xsl:attribute name=\"m:val\">p</xsl:attribute>\n                </xsl:element>\n            </xsl:when>\n            <xsl:when test=\"$font='bold-fraktur'\">\n                <xsl:element name=\"m:scr\">\n                    <xsl:attribute name=\"m:val\">fraktur</xsl:attribute>\n                </xsl:element>\n                <xsl:element name=\"m:sty\">\n                    <xsl:attribute name=\"m:val\">b</xsl:attribute>\n                </xsl:element>\n            </xsl:when>\n            <xsl:when test=\"$font='sans-serif'\">\n                <xsl:element name=\"m:scr\">\n                    <xsl:attribute name=\"m:val\">sans-serif</xsl:attribute>\n                </xsl:element>\n                <xsl:element name=\"m:sty\">\n                    <xsl:attribute name=\"m:val\">p</xsl:attribute>\n                </xsl:element>\n            </xsl:when>\n            <xsl:when test=\"$font='bold-sans-serif'\">\n                <xsl:element name=\"m:scr\">\n                    <xsl:attribute name=\"m:val\">sans-serif</xsl:attribute>\n                </xsl:element>\n                <xsl:element name=\"m:sty\">\n                    <xsl:attribute name=\"m:val\">b</xsl:attribute>\n                </xsl:element>\n            </xsl:when>\n            <xsl:when test=\"$font='sans-serif-italic'\">\n                <xsl:element name=\"m:scr\">\n                    <xsl:attribute name=\"m:val\">sans-serif</xsl:attribute>\n                </xsl:element>\n            </xsl:when>\n            <xsl:when test=\"$font='sans-serif-bold-italic'\">\n                <xsl:element name=\"m:scr\">\n                    <xsl:attribute name=\"m:val\">sans-serif</xsl:attribute>\n                </xsl:element>\n                <xsl:element name=\"m:sty\">\n                    <xsl:attribute name=\"m:val\">bi</xsl:attribute>\n                </xsl:element>\n            </xsl:when>\n            <xsl:when test=\"$font='monospace'\"/>\n            <!-- We can't do monospace, so leave empty -->\n            <xsl:when test=\"$font='bold'\">\n                <xsl:element name=\"m:sty\">\n                    <xsl:attribute name=\"m:val\">b</xsl:attribute>\n                </xsl:element>\n            </xsl:when>\n            <xsl:when test=\"$font='bi' or $font='bold-italic'\">\n                <xsl:element name=\"m:sty\">\n                    <xsl:attribute name=\"m:val\">bi</xsl:attribute>\n                </xsl:element>\n            </xsl:when>\n        </xsl:choose>\n    </xsl:template>\n\n    <xsl:template name=\"FBar\">\n        <xsl:param name=\"sLineThickness\"/>\n        <xsl:variable name=\"sLowerLineThickness\" select=\"translate($sLineThickness, $StrUCAlphabet, $StrLCAlphabet)\"/>\n        <xsl:choose>\n            <xsl:when test=\"string-length($sLowerLineThickness)=0\n                      or $sLowerLineThickness='thin'\n                      or $sLowerLineThickness='medium'\n                      or $sLowerLineThickness='thick'\">1</xsl:when>\n            <xsl:otherwise>\n                <xsl:variable name=\"fStrContainsNonZeroDigit\">\n                    <xsl:call-template name=\"FStrContainsNonZeroDigit\">\n                        <xsl:with-param name=\"s\" select=\"$sLowerLineThickness\"/>\n                    </xsl:call-template>\n                </xsl:variable>\n                <xsl:choose>\n                    <xsl:when test=\"$fStrContainsNonZeroDigit=1\">1</xsl:when>\n                    <xsl:otherwise>0</xsl:otherwise>\n                </xsl:choose>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n\n    <!-- %%Template: match mfrac\n\t\t-->\n    <xsl:template match=\"mml:mfrac\">\n        <xsl:variable name=\"fBar\">\n            <xsl:call-template name=\"FBar\">\n                <xsl:with-param name=\"sLineThickness\">\n                    <xsl:choose>\n                        <xsl:when test=\"@linethickness\">\n                            <xsl:value-of select=\"@linethickness\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"@mml:linethickness\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n            </xsl:call-template>\n        </xsl:variable>\n\n        <xsl:element name=\"m:f\">\n            <xsl:element name=\"m:fPr\">\n                <xsl:element name=\"m:type\">\n                    <xsl:attribute name=\"m:val\">\n                        <xsl:choose>\n                            <xsl:when test=\"$fBar=0\">noBar</xsl:when>\n                            <xsl:when test=\"@bevelled='true' or @mml:bevelled='true'\">skw</xsl:when>\n                            <xsl:otherwise>bar</xsl:otherwise>\n                        </xsl:choose>\n                    </xsl:attribute>\n                </xsl:element>\n            </xsl:element>\n            <xsl:element name=\"m:num\">\n                <xsl:call-template name=\"CreateArgProp\"/>\n                <xsl:apply-templates select=\"child::*[1]\"/>\n            </xsl:element>\n            <xsl:element name=\"m:den\">\n                <xsl:call-template name=\"CreateArgProp\"/>\n                <xsl:apply-templates select=\"child::*[2]\"/>\n            </xsl:element>\n        </xsl:element>\n    </xsl:template>\n\n    <!-- %%Template: match menclose msqrt\n\t-->\n    <xsl:template match=\"mml:menclose | mml:msqrt\">\n        <xsl:variable name=\"sLowerCaseNotation\">\n            <xsl:choose>\n                <xsl:when test=\"@notation\">\n                    <xsl:value-of select=\"translate(@notation, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:value-of select=\"translate(@mml:notation, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n        <xsl:choose>\n            <!-- Take care of default -->\n            <xsl:when test=\"$sLowerCaseNotation='radical'\n                      or not($sLowerCaseNotation)\n                      or $sLowerCaseNotation=''\n                      or self::mml:msqrt\">\n                <xsl:element name=\"m:rad\">\n                    <xsl:element name=\"m:radPr\">\n                        <xsl:element name=\"m:degHide\">\n                            <xsl:attribute name=\"m:val\">on</xsl:attribute>\n                        </xsl:element>\n                    </xsl:element>\n                    <xsl:element name=\"m:deg\">\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                    </xsl:element>\n                    <xsl:element name=\"m:e\">\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"*\"/>\n                    </xsl:element>\n                </xsl:element>\n            </xsl:when>\n            <xsl:otherwise>\n                <xsl:choose>\n                    <xsl:when test=\"$sLowerCaseNotation='actuarial' or $sLowerCaseNotation='longdiv'\"/>\n                    <xsl:otherwise>\n                        <xsl:element name=\"m:borderBox\">\n                            <!-- Dealing with more complex notation attribute -->\n                            <xsl:variable name=\"fBox\">\n                                <xsl:choose>\n                                    <!-- Word doesn't have circle and roundedbox concepts, therefore, map both to a\n                       box. -->\n                                    <xsl:when test=\"contains($sLowerCaseNotation, 'box')\n                                  or contains($sLowerCaseNotation, 'circle')\n                                  or contains($sLowerCaseNotation, 'roundedbox')\">1</xsl:when>\n                                    <xsl:otherwise>0</xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:variable>\n                            <xsl:variable name=\"fTop\">\n                                <xsl:choose>\n                                    <xsl:when test=\"contains($sLowerCaseNotation, 'top')\">1</xsl:when>\n                                    <xsl:otherwise>0</xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:variable>\n                            <xsl:variable name=\"fBot\">\n                                <xsl:choose>\n                                    <xsl:when test=\"contains($sLowerCaseNotation, 'bottom')\">1</xsl:when>\n                                    <xsl:otherwise>0</xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:variable>\n                            <xsl:variable name=\"fLeft\">\n                                <xsl:choose>\n                                    <xsl:when test=\"contains($sLowerCaseNotation, 'left')\">1</xsl:when>\n                                    <xsl:otherwise>0</xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:variable>\n                            <xsl:variable name=\"fRight\">\n                                <xsl:choose>\n                                    <xsl:when test=\"contains($sLowerCaseNotation, 'right')\">1</xsl:when>\n                                    <xsl:otherwise>0</xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:variable>\n                            <xsl:variable name=\"fStrikeH\">\n                                <xsl:choose>\n                                    <xsl:when test=\"contains($sLowerCaseNotation, 'horizontalstrike')\">1</xsl:when>\n                                    <xsl:otherwise>0</xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:variable>\n                            <xsl:variable name=\"fStrikeV\">\n                                <xsl:choose>\n                                    <xsl:when test=\"contains($sLowerCaseNotation, 'verticalstrike')\">1</xsl:when>\n                                    <xsl:otherwise>0</xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:variable>\n                            <xsl:variable name=\"fStrikeBLTR\">\n                                <xsl:choose>\n                                    <xsl:when test=\"contains($sLowerCaseNotation, 'updiagonalstrike')\">1</xsl:when>\n                                    <xsl:otherwise>0</xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:variable>\n                            <xsl:variable name=\"fStrikeTLBR\">\n                                <xsl:choose>\n                                    <xsl:when test=\"contains($sLowerCaseNotation, 'downdiagonalstrike')\">1</xsl:when>\n                                    <xsl:otherwise>0</xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:variable>\n\n                            <!-- Should we create borderBoxPr?\n                   We should if the enclosure isn't Word's default, which is\n                   a plain box -->\n                            <xsl:if test=\"$fStrikeH=1\n                          or $fStrikeV=1\n                          or $fStrikeBLTR=1\n                          or $fStrikeTLBR=1\n                          or ($fBox=0\n                              and not($fTop=1\n                                      and $fBot=1\n                                      and $fLeft=1\n                                      and $fRight=1)\n                              )\">\n                                <xsl:element name=\"m:borderBoxPr\">\n                                    <xsl:if test=\"$fBox=0\">\n                                        <xsl:if test=\"$fTop=0\">\n                                            <xsl:element name=\"m:hideTop\">\n                                                <xsl:attribute name=\"m:val\">on</xsl:attribute>\n                                            </xsl:element>\n                                        </xsl:if>\n                                        <xsl:if test=\"$fBot=0\">\n                                            <xsl:element name=\"m:hideBot\">\n                                                <xsl:attribute name=\"m:val\">on</xsl:attribute>\n                                            </xsl:element>\n                                        </xsl:if>\n                                        <xsl:if test=\"$fLeft=0\">\n                                            <xsl:element name=\"m:hideLeft\">\n                                                <xsl:attribute name=\"m:val\">on</xsl:attribute>\n                                            </xsl:element>\n                                        </xsl:if>\n                                        <xsl:if test=\"$fRight=0\">\n                                            <xsl:element name=\"m:hideRight\">\n                                                <xsl:attribute name=\"m:val\">on</xsl:attribute>\n                                            </xsl:element>\n                                        </xsl:if>\n                                    </xsl:if>\n                                    <xsl:if test=\"$fStrikeH=1\">\n                                        <xsl:element name=\"m:strikeH\">\n                                            <xsl:attribute name=\"m:val\">on</xsl:attribute>\n                                        </xsl:element>\n                                    </xsl:if>\n                                    <xsl:if test=\"$fStrikeV=1\">\n                                        <xsl:element name=\"m:strikeV\">\n                                            <xsl:attribute name=\"m:val\">on</xsl:attribute>\n                                        </xsl:element>\n                                    </xsl:if>\n                                    <xsl:if test=\"$fStrikeBLTR=1\">\n                                        <xsl:element name=\"m:strikeBLTR\">\n                                            <xsl:attribute name=\"m:val\">on</xsl:attribute>\n                                        </xsl:element>\n                                    </xsl:if>\n                                    <xsl:if test=\"$fStrikeTLBR=1\">\n                                        <xsl:element name=\"m:strikeTLBR\">\n                                            <xsl:attribute name=\"m:val\">on</xsl:attribute>\n                                        </xsl:element>\n                                    </xsl:if>\n                                </xsl:element>\n                            </xsl:if>\n                            <xsl:element name=\"m:e\">\n                                <xsl:call-template name=\"CreateArgProp\"/>\n                                <xsl:apply-templates select=\"*\"/>\n                            </xsl:element>\n                        </xsl:element>\n                    </xsl:otherwise>\n                </xsl:choose>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- %%Template: CreateArgProp\n\t-->\n    <xsl:template name=\"CreateArgProp\">\n        <xsl:if test=\"not(count(ancestor-or-self::mml:mstyle[@scriptlevel='0' or @scriptlevel='1' or @scriptlevel='2'])=0)\n                  or not(count(ancestor-or-self::mml:mstyle[@mml:scriptlevel='0' or @mml:scriptlevel='1' or @mml:scriptlevel='2'])=0)\">\n            <xsl:element name=\"m:argPr\">\n                <xsl:element name=\"m:scrLvl\">\n                    <xsl:attribute name=\"m:val\">\n                        <xsl:choose>\n                            <xsl:when test=\"ancestor-or-self::mml:mstyle[@scriptlevel][1]/@scriptlevel\">\n                                <xsl:value-of select=\"ancestor-or-self::mml:mstyle[@scriptlevel][1]/@scriptlevel\"/>\n                            </xsl:when>\n                            <xsl:otherwise>\n                                <xsl:value-of select=\"ancestor-or-self::mml:mstyle[@scriptlevel][1]/@mml:scriptlevel\"/>\n                            </xsl:otherwise>\n                        </xsl:choose>\n                    </xsl:attribute>\n                </xsl:element>\n            </xsl:element>\n        </xsl:if>\n    </xsl:template>\n\n    <!-- %%Template: match mroot\n\t-->\n    <xsl:template match=\"mml:mroot\">\n        <xsl:element name=\"m:rad\">\n            <xsl:element name=\"m:radPr\">\n                <xsl:element name=\"m:degHide\">\n                    <xsl:attribute name=\"m:val\">off</xsl:attribute>\n                </xsl:element>\n            </xsl:element>\n            <xsl:element name=\"m:deg\">\n                <xsl:call-template name=\"CreateArgProp\"/>\n                <xsl:apply-templates select=\"child::*[2]\"/>\n            </xsl:element>\n            <xsl:element name=\"m:e\">\n                <xsl:call-template name=\"CreateArgProp\"/>\n                <xsl:apply-templates select=\"child::*[1]\"/>\n            </xsl:element>\n        </xsl:element>\n    </xsl:template>\n\n    <!-- MathML has no concept of a linear fraction.  When transforming a linear fraction\n       from Omml to MathML, we create the following MathML:\n\n       <mml:mrow>\n         <mml:mrow>\n            // numerator\n         </mml:mrow>\n         <mml:mo>/</mml:mo>\n         <mml:mrow>\n            // denominator\n         </mml:mrow>\n       </mml:mrow>\n\n       This template looks for four things:\n          1.  ndCur is an mml:mrow\n          2.  ndCur has three children\n          3.  The second child is an <mml:mo>\n          4.  The second child's text is '/'\n\n       -->\n    <xsl:template name=\"FLinearFrac\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n        <xsl:variable name=\"sNdText\">\n            <xsl:value-of select=\"normalize-space($ndCur/*[2])\"/>\n        </xsl:variable>\n\n        <xsl:choose>\n            <!-- I spy a linear fraction -->\n            <xsl:when test=\"$ndCur/self::mml:mrow\n                      and count($ndCur/*)=3\n                      and $ndCur/*[2][self::mml:mo]\n                      and $sNdText='/'\">1</xsl:when>\n            <xsl:otherwise>0</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n\n    <!-- Though presentation mathml can certainly typeset any generic function with the\n\t     appropriate function operator spacing, presentation MathML has no concept of\n\t\t\t a function structure like omml does.  In order to preserve the omml <func>\n\t\t\t element, we must establish how an omml <func> element looks in mml.  This\n\t\t\t is shown below:\n\n       <mml:mrow>\n         <mml:mrow>\n            // function name\n         </mml:mrow>\n         <mml:mo>&#x02061;</mml:mo>\n         <mml:mrow>\n            // function argument\n         </mml:mrow>\n       </mml:mrow>\n\n       This template looks for six things to be true:\n\t\t\t\t\t1.  ndCur is an mml:mrow\n\t\t\t\t\t2.  ndCur has three children\n\t\t\t\t\t3.  The first child is an <mml:mrow>\n\t\t\t\t\t4.  The second child is an <mml:mo>\n\t\t\t\t\t5.  The third child is an <mml:mrow>\n\t\t\t\t\t6.  The second child's text is '&#x02061;'\n       -->\n    <xsl:template name=\"FIsFunc\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n        <xsl:variable name=\"sNdText\">\n            <xsl:value-of select=\"normalize-space($ndCur/*[2])\"/>\n        </xsl:variable>\n\n        <xsl:choose>\n            <!-- Is this an omml function -->\n            <xsl:when test=\"count($ndCur/*)=3\n\t\t\t\t\t\t\t\t      and $ndCur/self::*[self::mml:mrow]\n                      and $ndCur/*[2][self::mml:mo]\n                      and $sNdText='&#x02061;'\">1</xsl:when>\n            <xsl:otherwise>0</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n\n    <!-- Given the node of the linear fraction's parent mrow,\n       make a linear fraction -->\n    <xsl:template name=\"MakeLinearFraction\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n        <xsl:element name=\"m:f\">\n            <xsl:element name=\"m:fPr\">\n                <xsl:element name=\"m:type\">\n                    <xsl:attribute name=\"m:val\">lin</xsl:attribute>\n                </xsl:element>\n            </xsl:element>\n            <xsl:element name=\"m:num\">\n                <xsl:call-template name=\"CreateArgProp\"/>\n                <xsl:apply-templates select=\"$ndCur/*[1]\"/>\n            </xsl:element>\n            <xsl:element name=\"m:den\">\n                <xsl:call-template name=\"CreateArgProp\"/>\n                <xsl:apply-templates select=\"$ndCur/*[3]\"/>\n            </xsl:element>\n        </xsl:element>\n    </xsl:template>\n\n\n    <!-- Given the node of the function's parent mrow,\n       make an omml function -->\n    <xsl:template name=\"WriteFunc\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n\n        <xsl:element name=\"m:func\">\n            <xsl:element name=\"m:fName\">\n                <xsl:apply-templates select=\"$ndCur/child::*[1]\"/>\n            </xsl:element>\n            <xsl:element name=\"m:e\">\n                <xsl:apply-templates select=\"$ndCur/child::*[3]\"/>\n            </xsl:element>\n        </xsl:element>\n    </xsl:template>\n\n\n    <!-- MathML doesn't have the concept of nAry structures.  The best approximation\n       to these is to have some under/over or sub/sup followed by an mrow or mstyle.\n\n       In the case that we've come across some under/over or sub/sup that contains an\n       nAry operator, this function handles the following sibling to the nAry structure.\n\n       If the following sibling is:\n\n          mml:mstyle, then apply templates to the children of this mml:mstyle\n\n          mml:mrow, determine if this mrow is a linear fraction\n          (see comments for FlinearFrac template).\n              If so, make an Omml linear fraction.\n              If not, apply templates as was done for mml:mstyle.\n\n       -->\n    <xsl:template name=\"NaryHandleMrowMstyle\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n        <!-- if the next sibling is an mrow, pull it in by\n\t\t\t\t\t\t\tdoing whatever we would have done to its children.\n\t\t\t\t\t\t\tThe mrow itself will be skipped, see template above. -->\n        <xsl:choose>\n            <xsl:when test=\"$ndCur[self::mml:mrow]\">\n                <!-- Check for linear fraction -->\n                <xsl:variable name=\"fLinearFrac\">\n                    <xsl:call-template name=\"FLinearFrac\">\n                        <xsl:with-param name=\"ndCur\" select=\"$ndCur\"/>\n                    </xsl:call-template>\n                </xsl:variable>\n                <xsl:choose>\n                    <xsl:when test=\"$fLinearFrac=1\">\n                        <xsl:call-template name=\"MakeLinearFraction\">\n                            <xsl:with-param name=\"ndCur\" select=\"$ndCur\"/>\n                        </xsl:call-template>\n                    </xsl:when>\n                    <xsl:otherwise>\n                        <xsl:variable name=\"fFunc\">\n                            <xsl:call-template name=\"FIsFunc\">\n                                <xsl:with-param name=\"ndCur\" select=\".\"/>\n                            </xsl:call-template>\n                        </xsl:variable>\n                        <xsl:choose>\n                            <xsl:when test=\"$fFunc=1\">\n                                <xsl:call-template name=\"WriteFunc\">\n                                    <xsl:with-param name=\"ndCur\" select=\".\"/>\n                                </xsl:call-template>\n                            </xsl:when>\n                            <xsl:otherwise>\n                                <xsl:apply-templates select=\"$ndCur/*\"/>\n                            </xsl:otherwise>\n                        </xsl:choose>\n                    </xsl:otherwise>\n                </xsl:choose>\n            </xsl:when>\n            <xsl:when test=\"$ndCur[self::mml:mstyle]\">\n                <xsl:apply-templates select=\"$ndCur/*\"/>\n            </xsl:when>\n        </xsl:choose>\n    </xsl:template>\n\n\n    <!-- MathML munder/mover can represent several Omml constructs\n       (m:bar, m:limLow, m:limUpp, m:acc, m:groupChr, etc.).  The following\n       templates (FIsBar, FIsAcc, and FIsGroupChr) are used to determine\n\t\t\t which of these Omml constructs an munder/mover should be translated into. -->\n\n    <!-- Note:  ndCur should only be an munder/mover MathML element.\n\n       ndCur should be interpretted as an m:bar if\n          1)  its respective accent attribute is not true\n          2)  its second child is an mml:mo\n          3)  the character of the mml:mo is the correct under/over bar. -->\n    <xsl:template name=\"FIsBar\">\n        <xsl:param name=\"ndCur\"/>\n        <xsl:variable name=\"fUnder\">\n            <xsl:choose>\n                <xsl:when test=\"$ndCur[self::mml:munder]\">1</xsl:when>\n                <xsl:otherwise>0</xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n        <xsl:variable name=\"sLowerCaseAccent\">\n            <xsl:choose>\n                <xsl:when test=\"$fUnder=1\">\n                    <xsl:choose>\n                        <xsl:when test=\"$ndCur/@accentunder\">\n                            <xsl:value-of select=\"translate($ndCur/@accentunder, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"translate($ndCur/@mml:accentunder, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:choose>\n                        <xsl:when test=\"$ndCur/@accent\">\n                            <xsl:value-of select=\"translate($ndCur/@accent, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"translate($ndCur/@mml:accent, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n        <xsl:variable name=\"fAccent\">\n            <xsl:choose>\n                <xsl:when test=\"$sLowerCaseAccent='true'\">1</xsl:when>\n                <xsl:otherwise>0</xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n\n        <xsl:choose>\n            <!-- The script is unaccented and the second child is an mo -->\n            <xsl:when test=\"$fAccent = 0\n                      and $ndCur/child::*[2]/self::mml:mo\">\n                <xsl:variable name=\"sOperator\">\n                    <xsl:value-of select=\"$ndCur/child::*[2]\"/>\n                </xsl:variable>\n                <xsl:choose>\n                    <!-- Should we write an underbar? -->\n                    <xsl:when test=\"$fUnder = 1\">\n                        <xsl:choose>\n                            <xsl:when test=\"$sOperator = '&#x0332;' or $sOperator = '&#x005F;'\">1</xsl:when>\n                            <xsl:otherwise>0</xsl:otherwise>\n                        </xsl:choose>\n                    </xsl:when>\n                    <!-- Should we write an overbar? -->\n                    <xsl:otherwise>\n                        <xsl:choose>\n                            <xsl:when test=\"$sOperator = '&#x0305;' or $sOperator = '&#x00AF;'\">1</xsl:when>\n                            <xsl:otherwise>0</xsl:otherwise>\n                        </xsl:choose>\n                    </xsl:otherwise>\n                </xsl:choose>\n            </xsl:when>\n            <xsl:otherwise>0</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- Note:  ndCur should only be an mover MathML element.\n\n       ndCur should be interpretted as an m:acc if\n          1)  its accent attribute is true\n          2)  its second child is an mml:mo\n          3)  there is only zero or one character in the mml:mo -->\n    <xsl:template name=\"FIsAcc\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n\n        <xsl:variable name=\"sLowerCaseAccent\">\n            <xsl:choose>\n                <xsl:when test=\"$ndCur/@accent\">\n                    <xsl:value-of select=\"translate($ndCur/@accent, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:value-of select=\"translate($ndCur/@mml:accent, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n        <xsl:variable name=\"sLowerCaseMoAccent\">\n            <xsl:choose>\n                <xsl:when test=\"$ndCur/child::*[2] = mml:mo and $ndCur/child::*[2]/@accent\">\n                    <xsl:value-of select=\"translate($ndCur/child::*[2]/@accent, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                </xsl:when>\n                <xsl:when test=\"$ndCur/child::*[2] = mml:mo and $ndCur/child::*[2]/@mml:accent\">\n                    <xsl:value-of select=\"translate($ndCur/child::*[2]/@mml:accent, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                </xsl:when>\n            </xsl:choose>\n        </xsl:variable>\n        <xsl:variable name=\"fAccent\">\n            <xsl:choose>\n                <xsl:when test=\"$sLowerCaseMoAccent='true' or ($sLowerCaseMoAccent='' and $sLowerCaseAccent='true')\">1</xsl:when>\n                <xsl:otherwise>0</xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n\n        <xsl:choose>\n            <!-- The script is accented and the second child is an mo -->\n            <xsl:when test=\"$fAccent = 1\n                      and $ndCur/child::*[2] = mml:mo\">\n                <xsl:variable name=\"sOperator\">\n                    <xsl:value-of select=\"$ndCur/child::*[2]\"/>\n                </xsl:variable>\n                <xsl:choose>\n                    <!-- There is only one operator, this is a valid Omml accent! -->\n                    <xsl:when test=\"string-length($sOperator) &lt;= 1\">1</xsl:when>\n                    <!-- More than one accented operator.  This isn't a valid\n               omml accent -->\n                    <xsl:otherwise>0</xsl:otherwise>\n                </xsl:choose>\n            </xsl:when>\n            <!-- Not accented, not an operator, or both, but in any case, this is\n           not an Omml accent. -->\n            <xsl:otherwise>0</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n\n    <!-- Is ndCur a groupChr?\n\t\t\t ndCur is a groupChr if:\n\n\t\t\t\t 1.  The accent is false (note:  accent attribute\n\t\t\t\t\t\t for munder is accentunder).\n\t\t\t\t 2.  ndCur is an munder or mover.\n\t\t\t\t 3.  ndCur has two children\n\t\t\t\t 4.  Of these two children, one is an mml:mo and the other is an mml:mrow\n\t\t\t\t 5.  The number of characters in the mml:mo is 1.\n\n\t\t\t If all of the above are true, then return 1, else return 0.\n\t-->\n    <xsl:template name=\"FIsGroupChr\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n        <xsl:variable name=\"fUnder\">\n            <xsl:choose>\n                <xsl:when test=\"$ndCur[self::mml:munder]\">1</xsl:when>\n                <xsl:otherwise>0</xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n        <xsl:variable name=\"sLowerCaseAccent\">\n            <xsl:choose>\n                <xsl:when test=\"$fUnder=1\">\n                    <xsl:choose>\n                        <xsl:when test=\"$ndCur/@accentunder\">\n                            <xsl:value-of select=\"translate($ndCur/@accentunder, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"translate($ndCur/@mml:accentunder, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:choose>\n                        <xsl:when test=\"$ndCur/@accent\">\n                            <xsl:value-of select=\"translate($ndCur/@accent, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"translate($ndCur/@mml:accent, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n\n        <xsl:variable name=\"fAccentFalse\">\n            <xsl:choose>\n                <xsl:when test=\"$sLowerCaseAccent='false'\">1</xsl:when>\n                <xsl:otherwise>0</xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n\n        <xsl:choose>\n            <xsl:when test=\"$fAccentFalse=1\n                      and $ndCur[self::mml:munder or self::mml:mover]\n                      and count($ndCur/child::*)=2\n                      and (($ndCur/child::*[1][self::mml:mrow] and $ndCur/child::*[2][self::mml:mo])\n                            or ($ndCur/child::*[1][self::mml:mo] and $ndCur/child::*[2][self::mml:mrow]))\">\n                <xsl:variable name=\"sOperator\">\n                    <xsl:value-of select=\"$ndCur/child::mml:mo\"/>\n                </xsl:variable>\n                <xsl:choose>\n                    <xsl:when test=\"string-length($sOperator) &lt;= 1\">1</xsl:when>\n                    <xsl:otherwise>0</xsl:otherwise>\n                </xsl:choose>\n            </xsl:when>\n\n            <xsl:otherwise>0</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n\n    <!-- %%Template: match munder\n\t-->\n    <xsl:template match=\"mml:munder\">\n        <xsl:variable name=\"fNary\">\n            <xsl:call-template name=\"isNary\">\n                <xsl:with-param name=\"ndCur\" select=\"child::*[1]\"/>\n            </xsl:call-template>\n        </xsl:variable>\n        <xsl:choose>\n            <xsl:when test=\"$fNary='true'\">\n                <m:nary>\n                    <xsl:call-template name=\"CreateNaryProp\">\n                        <xsl:with-param name=\"chr\">\n                            <xsl:value-of select=\"normalize-space(child::*[1])\"/>\n                        </xsl:with-param>\n                        <xsl:with-param name=\"sMathmlType\" select=\"'munder'\"/>\n                    </xsl:call-template>\n                    <m:sub>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[2]\"/>\n                    </m:sub>\n                    <m:sup>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                    </m:sup>\n                    <m:e>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:call-template name=\"NaryHandleMrowMstyle\">\n                            <xsl:with-param name=\"ndCur\" select=\"following-sibling::*[1]\"/>\n                        </xsl:call-template>\n                    </m:e>\n                </m:nary>\n            </xsl:when>\n            <xsl:otherwise>\n                <!-- Should this munder be interpreted as an OMML m:bar? -->\n                <xsl:variable name=\"fIsBar\">\n                    <xsl:call-template name=\"FIsBar\">\n                        <xsl:with-param name=\"ndCur\" select=\".\"/>\n                    </xsl:call-template>\n                </xsl:variable>\n                <xsl:choose>\n                    <xsl:when test=\"$fIsBar=1\">\n                        <m:bar>\n                            <m:barPr>\n                                <m:pos m:val=\"bot\"/>\n                            </m:barPr>\n                            <m:e>\n                                <xsl:call-template name=\"CreateArgProp\"/>\n                                <xsl:apply-templates select=\"child::*[1]\"/>\n                            </m:e>\n                        </m:bar>\n                    </xsl:when>\n                    <xsl:otherwise>\n                        <!-- It isn't an integral or underbar, is this a groupChr? -->\n                        <xsl:variable name=\"fGroupChr\">\n                            <xsl:call-template name=\"FIsGroupChr\">\n                                <xsl:with-param name=\"ndCur\" select=\".\"/>\n                            </xsl:call-template>\n                        </xsl:variable>\n                        <xsl:choose>\n                            <xsl:when test=\"$fGroupChr=1\">\n                                <xsl:element name=\"m:groupChr\">\n                                    <xsl:call-template name=\"CreateGroupChrPr\">\n                                        <xsl:with-param name=\"chr\">\n                                            <xsl:value-of select=\"mml:mo\"/>\n                                        </xsl:with-param>\n                                        <xsl:with-param name=\"pos\">\n                                            <xsl:choose>\n                                                <xsl:when test=\"child::*[1][self::mml:mrow]\">bot</xsl:when>\n                                                <xsl:otherwise>top</xsl:otherwise>\n                                            </xsl:choose>\n                                        </xsl:with-param>\n                                        <xsl:with-param name=\"vertJc\">top</xsl:with-param>\n                                    </xsl:call-template>\n                                    <xsl:element name=\"m:e\">\n                                        <xsl:apply-templates select=\"mml:mrow\"/>\n                                    </xsl:element>\n                                </xsl:element>\n                            </xsl:when>\n                            <xsl:otherwise>\n                                <!-- Generic munder -->\n                                <xsl:element name=\"m:limLow\">\n                                    <xsl:element name=\"m:e\">\n                                        <xsl:call-template name=\"CreateArgProp\"/>\n                                        <xsl:apply-templates select=\"child::*[1]\"/>\n                                    </xsl:element>\n                                    <xsl:element name=\"m:lim\">\n                                        <xsl:call-template name=\"CreateArgProp\"/>\n                                        <xsl:apply-templates select=\"child::*[2]\"/>\n                                    </xsl:element>\n                                </xsl:element>\n                            </xsl:otherwise>\n                        </xsl:choose>\n                    </xsl:otherwise>\n                </xsl:choose>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n\n    <!-- Given the values for chr, pos, and vertJc, create an omml\n\t     groupChr's groupChrPr -->\n    <xsl:template name=\"CreateGroupChrPr\">\n        <xsl:param name=\"chr\">&#x23df;</xsl:param>\n        <xsl:param name=\"pos\" select=\"bot\"/>\n        <xsl:param name=\"vertJc\" select=\"top\"/>\n        <xsl:element name=\"m:groupChrPr\">\n            <xsl:element name=\"m:chr\">\n                <xsl:attribute name=\"m:val\">\n                    <xsl:value-of select=\"$chr\"/>\n                </xsl:attribute>\n            </xsl:element>\n            <xsl:element name=\"m:pos\">\n                <xsl:attribute name=\"m:val\">\n                    <xsl:value-of select=\"$pos\"/>\n                </xsl:attribute>\n            </xsl:element>\n            <xsl:element name=\"m:vertJc\">\n                <xsl:attribute name=\"m:val\">\n                    <xsl:value-of select=\"$vertJc\"/>\n                </xsl:attribute>\n            </xsl:element>\n        </xsl:element>\n    </xsl:template>\n\n\n    <!--\n      Convert a non-combining character into its upper combining\n      couterpart.\n\n      { Non-combining, Upper-combining }\n      {U+02D8, U+0306}, // BREVE\n      {U+00B8, U+0312}, // CEDILLA\n      {U+0060, U+0300}, // GRAVE ACCENT\n      {U+002D, U+0305}, // HYPHEN-MINUS/OVERLINE\n      {U+2212, U+0305}, // MINUS SIGN/OVERLINE\n      {U+002E, U+0305}, // FULL STOP/DOT ABOVE\n      {U+02D9, U+0307}, // DOT ABOVE\n      {U+02DD, U+030B}, // DOUBLE ACUTE ACCENT\n      {U+00B4, U+0301}, // ACUTE ACCENT\n      {U+007E, U+0303}, // TILDE\n      {U+02DC, U+0303}, // SMALL TILDE\n      {U+00A8, U+0308}, // DIAERESIS\n      {U+02C7, U+030C}, // CARON\n      {U+005E, U+0302}, // CIRCUMFLEX ACCENT\n      {U+00AF, U+0305}, // MACRON\n      {U+005F, ::::::}, // LOW LINE\n      {U+2192, U+20D7}, // RIGHTWARDS ARROW\n      {U+27F6, U+20D7}, // LONG RIGHTWARDS ARROW\n      {U+2190, U+20D6}, // LEFT ARROW\n  -->\n    <xsl:template name=\"ToUpperCombining\">\n        <xsl:param name=\"ch\"/>\n        <xsl:choose>\n            <!-- BREVE -->\n            <xsl:when test=\"$ch='&#x02D8;'\">&#x0306;</xsl:when>\n            <!-- CEDILLA -->\n            <xsl:when test=\"$ch='&#x00B8;'\">&#x0312;</xsl:when>\n            <!-- GRAVE ACCENT -->\n            <xsl:when test=\"$ch='&#x0060;'\">&#x0300;</xsl:when>\n            <!-- HYPHEN-MINUS/OVERLINE -->\n            <xsl:when test=\"$ch='&#x002D;'\">&#x0305;</xsl:when>\n            <!-- MINUS SIGN/OVERLINE -->\n            <xsl:when test=\"$ch='&#x2212;'\">&#x0305;</xsl:when>\n            <!-- FULL STOP/DOT ABOVE -->\n            <xsl:when test=\"$ch='&#x002E;'\">&#x0307;</xsl:when>\n            <!-- DOT ABOVE -->\n            <xsl:when test=\"$ch='&#x02D9;'\">&#x0307;</xsl:when>\n            <!-- DOUBLE ACUTE ACCENT -->\n            <xsl:when test=\"$ch='&#x02DD;'\">&#x030B;</xsl:when>\n            <!-- ACUTE ACCENT -->\n            <xsl:when test=\"$ch='&#x00B4;'\">&#x0301;</xsl:when>\n            <!-- TILDE -->\n            <xsl:when test=\"$ch='&#x007E;'\">&#x0303;</xsl:when>\n            <!-- SMALL TILDE -->\n            <xsl:when test=\"$ch='&#x02DC;'\">&#x0303;</xsl:when>\n            <!-- DIAERESIS -->\n            <xsl:when test=\"$ch='&#x00A8;'\">&#x0308;</xsl:when>\n            <!-- CARON -->\n            <xsl:when test=\"$ch='&#x02C7;'\">&#x030C;</xsl:when>\n            <!-- CIRCUMFLEX ACCENT -->\n            <xsl:when test=\"$ch='&#x005E;'\">&#x0302;</xsl:when>\n            <!-- MACRON -->\n            <xsl:when test=\"$ch='&#x00AF;'\">&#x0305;</xsl:when>\n\n            <!-- LOW LINE -->\n\n            <!-- RIGHTWARDS ARROW -->\n            <xsl:when test=\"$ch='&#x2192;'\">&#x20D7;</xsl:when>\n            <!-- LONG RIGHTWARDS ARROW -->\n            <xsl:when test=\"$ch='&#x27F6;'\">&#x20D7;</xsl:when>\n            <!-- LEFT ARROW -->\n            <xsl:when test=\"$ch='&#x2190;'\">&#x20D6;</xsl:when>\n            <xsl:otherwise>\n                <xsl:value-of select=\"$ch\"/>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n\n    <!-- %%Template: match mover\n\t-->\n    <xsl:template match=\"mml:mover\">\n        <xsl:variable name=\"fNary\">\n            <xsl:call-template name=\"isNary\">\n                <xsl:with-param name=\"ndCur\" select=\"child::*[1]\"/>\n            </xsl:call-template>\n        </xsl:variable>\n        <xsl:choose>\n            <xsl:when test=\"$fNary='true'\">\n                <m:nary>\n                    <xsl:call-template name=\"CreateNaryProp\">\n                        <xsl:with-param name=\"chr\">\n                            <xsl:value-of select=\"normalize-space(child::*[1])\"/>\n                        </xsl:with-param>\n                        <xsl:with-param name=\"sMathmlType\" select=\"'mover'\"/>\n                    </xsl:call-template>\n                    <m:sub>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                    </m:sub>\n                    <m:sup>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[2]\"/>\n                    </m:sup>\n                    <m:e>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:call-template name=\"NaryHandleMrowMstyle\">\n                            <xsl:with-param name=\"ndCur\" select=\"following-sibling::*[1]\"/>\n                        </xsl:call-template>\n                    </m:e>\n                </m:nary>\n            </xsl:when>\n            <xsl:otherwise>\n                <!-- Should this munder be interpreted as an OMML m:bar or m:acc? -->\n\n                <!-- Check to see if this is an m:bar -->\n                <xsl:variable name=\"fIsBar\">\n                    <xsl:call-template name=\"FIsBar\">\n                        <xsl:with-param name=\"ndCur\" select=\".\"/>\n                    </xsl:call-template>\n                </xsl:variable>\n                <xsl:choose>\n                    <xsl:when test=\"$fIsBar = 1\">\n                        <m:bar>\n                            <m:barPr>\n                                <m:pos m:val=\"top\"/>\n                            </m:barPr>\n                            <m:e>\n                                <xsl:call-template name=\"CreateArgProp\"/>\n                                <xsl:apply-templates select=\"child::*[1]\"/>\n                            </m:e>\n                        </m:bar>\n                    </xsl:when>\n                    <xsl:otherwise>\n                        <!-- Not an m:bar, should it be an m:acc? -->\n                        <xsl:variable name=\"fIsAcc\">\n                            <xsl:call-template name=\"FIsAcc\">\n                                <xsl:with-param name=\"ndCur\" select=\".\"/>\n                            </xsl:call-template>\n                        </xsl:variable>\n                        <xsl:choose>\n                            <xsl:when test=\"$fIsAcc=1\">\n                                <m:acc>\n                                    <m:accPr>\n                                        <m:chr>\n                                            <xsl:variable name=\"ch\">\n                                                <xsl:value-of select=\"child::*[2]\"/>\n                                            </xsl:variable>\n                                            <xsl:variable name=\"chComb\">\n                                                <xsl:call-template name=\"ToUpperCombining\">\n                                                    <xsl:with-param name=\"ch\" select=\"$ch\"/>\n                                                </xsl:call-template>\n                                            </xsl:variable>\n                                            <xsl:attribute name=\"m:val\">\n                                                <xsl:value-of select=\"$chComb\"/>\n                                            </xsl:attribute>\n                                        </m:chr>\n                                    </m:accPr>\n                                    <m:e>\n                                        <xsl:call-template name=\"CreateArgProp\"/>\n                                        <xsl:apply-templates select=\"child::*[1]\"/>\n                                    </m:e>\n                                </m:acc>\n                            </xsl:when>\n                            <xsl:otherwise>\n                                <!-- This isn't an integral, overbar or accent,\n\t\t\t\t\t\t\t\t     could it be a groupChr? -->\n                                <xsl:variable name=\"fGroupChr\">\n                                    <xsl:call-template name=\"FIsGroupChr\">\n                                        <xsl:with-param name=\"ndCur\" select=\".\"/>\n                                    </xsl:call-template>\n                                </xsl:variable>\n                                <xsl:choose>\n                                    <xsl:when test=\"$fGroupChr=1\">\n                                        <xsl:element name=\"m:groupChr\">\n                                            <xsl:call-template name=\"CreateGroupChrPr\">\n                                                <xsl:with-param name=\"chr\">\n                                                    <xsl:value-of select=\"mml:mo\"/>\n                                                </xsl:with-param>\n                                                <xsl:with-param name=\"pos\">\n                                                    <xsl:choose>\n                                                        <xsl:when test=\"child::*[1][self::mml:mrow]\">top</xsl:when>\n                                                        <xsl:otherwise>bot</xsl:otherwise>\n                                                    </xsl:choose>\n                                                </xsl:with-param>\n                                                <xsl:with-param name=\"vertJc\">bot</xsl:with-param>\n                                            </xsl:call-template>\n                                            <xsl:element name=\"m:e\">\n                                                <xsl:apply-templates select=\"mml:mrow\"/>\n                                            </xsl:element>\n                                        </xsl:element>\n                                    </xsl:when>\n                                    <xsl:otherwise>\n                                        <!-- Generic mover -->\n                                        <xsl:element name=\"m:limUpp\">\n                                            <xsl:element name=\"m:e\">\n                                                <xsl:call-template name=\"CreateArgProp\"/>\n                                                <xsl:apply-templates select=\"child::*[1]\"/>\n                                            </xsl:element>\n                                            <xsl:element name=\"m:lim\">\n                                                <xsl:call-template name=\"CreateArgProp\"/>\n                                                <xsl:apply-templates select=\"child::*[2]\"/>\n                                            </xsl:element>\n                                        </xsl:element>\n                                    </xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:otherwise>\n                        </xsl:choose>\n                    </xsl:otherwise>\n                </xsl:choose>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n\n    <!-- %%Template: match munderover\n\t-->\n    <xsl:template match=\"mml:munderover\">\n        <xsl:variable name=\"fNary\">\n            <xsl:call-template name=\"isNary\">\n                <xsl:with-param name=\"ndCur\" select=\"child::*[1]\"/>\n            </xsl:call-template>\n        </xsl:variable>\n        <xsl:choose>\n            <xsl:when test=\"$fNary='true'\">\n                <m:nary>\n                    <xsl:call-template name=\"CreateNaryProp\">\n                        <xsl:with-param name=\"chr\">\n                            <xsl:value-of select=\"normalize-space(child::*[1])\"/>\n                        </xsl:with-param>\n                        <xsl:with-param name=\"sMathmlType\" select=\"'munderover'\"/>\n                    </xsl:call-template>\n                    <m:sub>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[2]\"/>\n                    </m:sub>\n                    <m:sup>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[3]\"/>\n                    </m:sup>\n                    <m:e>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:call-template name=\"NaryHandleMrowMstyle\">\n                            <xsl:with-param name=\"ndCur\" select=\"following-sibling::*[1]\"/>\n                        </xsl:call-template>\n                    </m:e>\n                </m:nary>\n            </xsl:when>\n            <xsl:otherwise>\n                <xsl:element name=\"m:limUpp\">\n                    <xsl:element name=\"m:e\">\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:element name=\"m:limLow\">\n                            <xsl:element name=\"m:e\">\n                                <xsl:call-template name=\"CreateArgProp\"/>\n                                <xsl:apply-templates select=\"child::*[1]\"/>\n                            </xsl:element>\n                            <xsl:element name=\"m:lim\">\n                                <xsl:call-template name=\"CreateArgProp\"/>\n                                <xsl:apply-templates select=\"child::*[2]\"/>\n                            </xsl:element>\n                        </xsl:element>\n                    </xsl:element>\n                    <xsl:element name=\"m:lim\">\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[3]\"/>\n                    </xsl:element>\n                </xsl:element>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- %%Template: match mfenced -->\n    <xsl:template match=\"mml:mfenced\">\n        <m:d>\n            <xsl:call-template name=\"CreateDelimProp\">\n                <xsl:with-param name=\"fChOpenValid\">\n                    <xsl:choose>\n                        <xsl:when test=\"@open\">\n                            <xsl:value-of select=\"1\"/>\n                        </xsl:when>\n                        <xsl:when test=\"@mml:open\">\n                            <xsl:value-of select=\"1\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"0\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n                <xsl:with-param name=\"chOpen\">\n                    <xsl:choose>\n                        <xsl:when test=\"@open\">\n                            <xsl:value-of select=\"@open\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"@mml:open\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n                <xsl:with-param name=\"fChSeparatorsValid\">\n                    <xsl:choose>\n                        <xsl:when test=\"@separators\">\n                            <xsl:value-of select=\"1\"/>\n                        </xsl:when>\n                        <xsl:when test=\"@mml:separators\">\n                            <xsl:value-of select=\"1\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"0\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n                <xsl:with-param name=\"chSeparators\">\n                    <xsl:choose>\n                        <xsl:when test=\"@separators\">\n                            <xsl:value-of select=\"@separators\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"@mml:separators\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n                <xsl:with-param name=\"fChCloseValid\">\n                    <xsl:choose>\n                        <xsl:when test=\"@close\">\n                            <xsl:value-of select=\"1\"/>\n                        </xsl:when>\n                        <xsl:when test=\"@mml:close\">\n                            <xsl:value-of select=\"1\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"0\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n                <xsl:with-param name=\"chClose\">\n                    <xsl:choose>\n                        <xsl:when test=\"@close\">\n                            <xsl:value-of select=\"@close\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"@mml:close\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:with-param>\n            </xsl:call-template>\n            <xsl:for-each select=\"*\">\n                <m:e>\n                    <xsl:call-template name=\"CreateArgProp\"/>\n                    <xsl:apply-templates select=\".\"/>\n                </m:e>\n            </xsl:for-each>\n        </m:d>\n    </xsl:template>\n\n    <!-- %%Template: CreateDelimProp\n\n\t\tGiven the characters to use as open, close and separators for\n\t\tthe delim object, create the m:dPr (delim properties).\n\n\t\tMathML can have any number of separators in an mfenced object, but\n\t\tOMML can only represent one separator for each d (delim) object.\n\t\tSo, we pick the first separator specified.\n\t-->\n    <xsl:template name=\"CreateDelimProp\">\n        <xsl:param name=\"fChOpenValid\"/>\n        <xsl:param name=\"chOpen\"/>\n        <xsl:param name=\"fChSeparatorsValid\"/>\n        <xsl:param name=\"chSeparators\"/>\n        <xsl:param name=\"fChCloseValid\"/>\n        <xsl:param name=\"chClose\"/>\n        <xsl:variable name=\"chSep\" select=\"substring($chSeparators, 1, 1)\"/>\n\n        <!-- do we need a dPr at all? If everything's at its default value, then\n\t\t\tdon't bother at all -->\n        <xsl:if test=\"($fChOpenValid=1 and not($chOpen = '(')) or\n\t\t\t\t\t\t  ($fChCloseValid=1 and not($chClose = ')')) or\n\t\t\t\t\t\t  not($chSep = '|')\">\n            <m:dPr>\n                <!-- the default for MathML and OMML is '('. -->\n                <xsl:if test=\"$fChOpenValid=1 and not($chOpen = '(')\">\n                    <m:begChr>\n                        <xsl:attribute name=\"m:val\">\n                            <xsl:value-of select=\"$chOpen\"/>\n                        </xsl:attribute>\n                    </m:begChr>\n                </xsl:if>\n\n                <!-- the default for MathML is ',' and for OMML is '|' -->\n\n                <xsl:choose>\n                    <!-- matches OMML's default, don't bother to write anything out -->\n                    <xsl:when test=\"$chSep = '|'\"/>\n\n                    <!-- Not specified, use MathML's default. We test against\n\t\t\t\t\tthe existence of the actual attribute, not the substring -->\n                    <xsl:when test=\"$fChSeparatorsValid=0\">\n                        <m:sepChr m:val=','/>\n                    </xsl:when>\n\n                    <xsl:otherwise>\n                        <m:sepChr>\n                            <xsl:attribute name=\"m:val\">\n                                <xsl:value-of select=\"$chSep\"/>\n                            </xsl:attribute>\n                        </m:sepChr>\n                    </xsl:otherwise>\n                </xsl:choose>\n\n                <!-- the default for MathML and OMML is ')'. -->\n                <xsl:if test=\"$fChCloseValid=1 and not($chClose = ')')\">\n                    <m:endChr>\n                        <xsl:attribute name=\"m:val\">\n                            <xsl:value-of select=\"$chClose\"/>\n                        </xsl:attribute>\n                    </m:endChr>\n                </xsl:if>\n            </m:dPr>\n        </xsl:if>\n    </xsl:template>\n\n    <xsl:template name=\"LQuoteFromMs\">\n        <xsl:param name=\"msCur\" select=\".\"/>\n        <xsl:choose>\n            <xsl:when test=\"(not($msCur/@lquote) or $msCur/@lquote='')\n                      and (not($msCur/@mml:lquote) or $msCur/@mml:lquote='')\">\n                <xsl:text>\"</xsl:text>\n            </xsl:when>\n            <xsl:otherwise>\n                <xsl:choose>\n                    <xsl:when test=\"$msCur/@lquote\">\n                        <xsl:value-of select=\"$msCur/@lquote\"/>\n                    </xsl:when>\n                    <xsl:otherwise>\n                        <xsl:value-of select=\"$msCur/@mml:lquote\"/>\n                    </xsl:otherwise>\n                </xsl:choose>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <xsl:template name=\"RQuoteFromMs\">\n        <xsl:param name=\"msCur\" select=\".\"/>\n        <xsl:choose>\n            <xsl:when test=\"(not($msCur/@rquote) or $msCur/@rquote='')\n                       and (not($msCur/@mml:rquote) or $msCur/@mml:rquote='')\">\n                <xsl:text>\"</xsl:text>\n            </xsl:when>\n            <xsl:otherwise>\n                <xsl:choose>\n                    <xsl:when test=\"$msCur/@rquote\">\n                        <xsl:value-of select=\"$msCur/@rquote\"/>\n                    </xsl:when>\n                    <xsl:otherwise>\n                        <xsl:value-of select=\"$msCur/@mml:rquote\"/>\n                    </xsl:otherwise>\n                </xsl:choose>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- %%Template: OutputMs\n\t-->\n    <xsl:template name=\"OutputMs\">\n        <xsl:param name=\"msCur\"/>\n\n        <xsl:variable name=\"chLquote\">\n            <xsl:call-template name=\"LQuoteFromMs\">\n                <xsl:with-param name=\"msCur\" select=\"$msCur\"/>\n            </xsl:call-template>\n        </xsl:variable>\n\n        <xsl:variable name=\"chRquote\">\n            <xsl:call-template name=\"RQuoteFromMs\">\n                <xsl:with-param name=\"msCur\" select=\"$msCur\"/>\n            </xsl:call-template>\n        </xsl:variable>\n\n        <xsl:value-of select=\"$chLquote\"/>\n        <xsl:value-of select=\"normalize-space($msCur)\"/>\n        <xsl:value-of select=\"$chRquote\"/>\n    </xsl:template>\n\n    <!-- %%Template: match msub\n\t-->\n    <xsl:template match=\"mml:msub\">\n        <xsl:variable name=\"fNary\">\n            <xsl:call-template name=\"isNary\">\n                <xsl:with-param name=\"ndCur\" select=\"child::*[1]\"/>\n            </xsl:call-template>\n        </xsl:variable>\n        <xsl:choose>\n            <xsl:when test=\"$fNary='true'\">\n                <m:nary>\n                    <xsl:call-template name=\"CreateNaryProp\">\n                        <xsl:with-param name=\"chr\">\n                            <xsl:value-of select=\"normalize-space(child::*[1])\"/>\n                        </xsl:with-param>\n                        <xsl:with-param name=\"sMathmlType\" select=\"'msub'\"/>\n                    </xsl:call-template>\n                    <m:sub>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[2]\"/>\n                    </m:sub>\n                    <m:sup>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                    </m:sup>\n                    <m:e>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:call-template name=\"NaryHandleMrowMstyle\">\n                            <xsl:with-param name=\"ndCur\" select=\"following-sibling::*[1]\"/>\n                        </xsl:call-template>\n                    </m:e>\n                </m:nary>\n            </xsl:when>\n            <xsl:otherwise>\n                <m:sSub>\n                    <m:e>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[1]\"/>\n                    </m:e>\n                    <m:sub>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[2]\"/>\n                    </m:sub>\n                </m:sSub>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- %%Template: match msup\n\t-->\n    <xsl:template match=\"mml:msup\">\n        <xsl:variable name=\"fNary\">\n            <xsl:call-template name=\"isNary\">\n                <xsl:with-param name=\"ndCur\" select=\"child::*[1]\"/>\n            </xsl:call-template>\n        </xsl:variable>\n        <xsl:choose>\n            <xsl:when test=\"$fNary='true'\">\n                <m:nary>\n                    <xsl:call-template name=\"CreateNaryProp\">\n                        <xsl:with-param name=\"chr\">\n                            <xsl:value-of select=\"normalize-space(child::*[1])\"/>\n                        </xsl:with-param>\n                        <xsl:with-param name=\"sMathmlType\" select=\"'msup'\"/>\n                    </xsl:call-template>\n                    <m:sub>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                    </m:sub>\n                    <m:sup>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[2]\"/>\n                    </m:sup>\n                    <m:e>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:call-template name=\"NaryHandleMrowMstyle\">\n                            <xsl:with-param name=\"ndCur\" select=\"following-sibling::*[1]\"/>\n                        </xsl:call-template>\n                    </m:e>\n                </m:nary>\n            </xsl:when>\n            <xsl:otherwise>\n                <m:sSup>\n                    <m:e>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[1]\"/>\n                    </m:e>\n                    <m:sup>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[2]\"/>\n                    </m:sup>\n                </m:sSup>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- %%Template: match msubsup\n\t-->\n    <xsl:template match=\"mml:msubsup\">\n        <xsl:variable name=\"fNary\">\n            <xsl:call-template name=\"isNary\">\n                <xsl:with-param name=\"ndCur\" select=\"child::*[1]\"/>\n            </xsl:call-template>\n        </xsl:variable>\n        <xsl:choose>\n            <xsl:when test=\"$fNary='true'\">\n                <m:nary>\n                    <xsl:call-template name=\"CreateNaryProp\">\n                        <xsl:with-param name=\"chr\">\n                            <xsl:value-of select=\"normalize-space(child::*[1])\"/>\n                        </xsl:with-param>\n                        <xsl:with-param name=\"sMathmlType\" select=\"'msubsup'\"/>\n                    </xsl:call-template>\n                    <m:sub>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[2]\"/>\n                    </m:sub>\n                    <m:sup>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[3]\"/>\n                    </m:sup>\n                    <m:e>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:call-template name=\"NaryHandleMrowMstyle\">\n                            <xsl:with-param name=\"ndCur\" select=\"following-sibling::*[1]\"/>\n                        </xsl:call-template>\n                    </m:e>\n                </m:nary>\n            </xsl:when>\n            <xsl:otherwise>\n                <m:sSubSup>\n                    <m:e>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[1]\"/>\n                    </m:e>\n                    <m:sub>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[2]\"/>\n                    </m:sub>\n                    <m:sup>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[3]\"/>\n                    </m:sup>\n                </m:sSubSup>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- %%Template: SplitScripts\n\n\t\tTakes an collection of nodes, and splits them\n\t\todd and even into sup and sub scripts. Used for dealing with\n\t\tmmultiscript.\n\n\t\tThis template assumes you want to output both a sub and sup element.\n\t\t-->\n    <xsl:template name=\"SplitScripts\">\n        <xsl:param name=\"ndScripts\"/>\n        <m:sub>\n            <xsl:call-template name=\"CreateArgProp\"/>\n            <xsl:apply-templates select=\"$ndScripts[(position() mod 2) = 1]\"/>\n        </m:sub>\n        <m:sup>\n            <xsl:call-template name=\"CreateArgProp\"/>\n            <xsl:apply-templates select=\"$ndScripts[(position() mod 2) = 0]\"/>\n        </m:sup>\n    </xsl:template>\n\n    <!-- %%Template: match mmultiscripts\n\n\t\tThere is some subtlety with the mml:mprescripts element. Everything that comes before\n\t\tthat is considered a script (as opposed to a pre-script), but it need not be present.\n\t-->\n    <xsl:template match=\"mml:mmultiscripts\">\n\n        <!-- count the nodes. Everything that comes after a mml:mprescripts is considered a pre-script;\n\t\t\tEverything that does not have an mml:mprescript as a preceding-sibling (and is not itself\n\t\t\tmml:mprescript) is a script, except for the first child which is always the base.\n\t\t\tThe mml:none element is a place holder for a sub/sup element slot.\n\n\t\t\tmmultisript pattern:\n\t\t\t<mmultiscript>\n\t\t\t\t(base)\n\t\t\t\t(sub sup)* // Where <none/> can replace a sub/sup entry to preserve pattern.\n\t\t\t\t<mprescripts />\n\t\t\t\t(presub presup)*\n\t\t\t</mmultiscript>\n\t\t\t-->\n        <!-- Count of presecript nodes that we'd print (this is essentially anything but the none placeholder. -->\n        <xsl:variable name=\"cndPrescriptStrict\"\n                      select=\"count(mml:mprescripts[1]/following-sibling::*[not(self::mml:none)])\"/>\n        <!-- Count of all super script excluding mml:none -->\n        <xsl:variable name=\"cndSuperScript\" select=\"count(*[not(preceding-sibling::mml:mprescripts)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tand not(self::mml:mprescripts)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tand ((position() mod 2) = 1)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tand not(self::mml:none)]) - 1\"/>\n        <!-- Count of all sup script excluding mml:none -->\n        <xsl:variable name=\"cndSubScript\" select=\"count(*[not(preceding-sibling::mml:mprescripts)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tand not(self::mml:mprescripts)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tand ((position() mod 2) = 0)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tand not(self::mml:none)])\"/>\n        <!-- Count of all scripts excluding mml:none -->\n        <xsl:variable name=\"cndScriptStrict\" select=\"$cndSuperScript + $cndSubScript\"/>\n        <!-- Count of all scripts including mml:none.  This is essentially all nodes before the\n\t\tfirst mml:mprescripts except the base. -->\n        <xsl:variable name=\"cndScript\"\n                      select=\"count(*[not(preceding-sibling::mml:mprescripts) and not(self::mml:mprescripts)]) - 1\"/>\n\n        <xsl:choose>\n            <!-- The easy case first. No prescripts, and no script ... just a base -->\n            <xsl:when test=\"$cndPrescriptStrict &lt;= 0 and $cndScriptStrict &lt;= 0\">\n                <xsl:apply-templates select=\"*[1]\"/>\n            </xsl:when>\n\n            <!-- Next, if there are no prescripts -->\n            <xsl:when test=\"$cndPrescriptStrict &lt;= 0\">\n                <!-- we know we have some scripts or else we would have taken the earlier\n\t\t\t\t\t  branch. -->\n                <xsl:choose>\n                    <!-- We have both sub and super scripts-->\n                    <xsl:when test=\"$cndSuperScript &gt; 0 and $cndSubScript &gt; 0\">\n                        <m:sSubSup>\n                            <m:e>\n                                <xsl:call-template name=\"CreateArgProp\"/>\n                                <xsl:apply-templates select=\"child::*[1]\"/>\n                            </m:e>\n\n                            <!-- Every child except the first is a script.  Do the split -->\n                            <xsl:call-template name=\"SplitScripts\">\n                                <xsl:with-param name=\"ndScripts\" select=\"*[position() &gt; 1]\"/>\n                            </xsl:call-template>\n                        </m:sSubSup>\n                    </xsl:when>\n                    <!-- Just a sub script -->\n                    <xsl:when test=\"$cndSubScript &gt; 0\">\n                        <m:sSub>\n                            <m:e>\n                                <xsl:call-template name=\"CreateArgProp\"/>\n                                <xsl:apply-templates select=\"child::*[1]\"/>\n                            </m:e>\n\n                            <!-- No prescripts and no super scripts, therefore, it's a sub. -->\n                            <m:sub>\n                                <xsl:apply-templates select=\"*[position() &gt; 1]\"/>\n                            </m:sub>\n                        </m:sSub>\n                    </xsl:when>\n                    <!-- Just super script -->\n                    <xsl:otherwise>\n                        <m:sSup>\n                            <m:e>\n                                <xsl:call-template name=\"CreateArgProp\"/>\n                                <xsl:apply-templates select=\"child::*[1]\"/>\n                            </m:e>\n\n                            <!-- No prescripts and no sub scripts, therefore, it's a sup. -->\n                            <m:sup>\n                                <xsl:apply-templates select=\"*[position() &gt; 1]\"/>\n                            </m:sup>\n                        </m:sSup>\n                    </xsl:otherwise>\n                </xsl:choose>\n            </xsl:when>\n\n            <!-- Next, if there are no scripts -->\n            <xsl:when test=\"$cndScriptStrict &lt;= 0\">\n                <!-- we know we have some prescripts or else we would have taken the earlier\n\t\t\t\t\t  branch. So, create an sPre and split the elements -->\n                <m:sPre>\n                    <m:e>\n                        <xsl:call-template name=\"CreateArgProp\"/>\n                        <xsl:apply-templates select=\"child::*[1]\"/>\n                    </m:e>\n\n                    <!-- The prescripts come after the mml:mprescript and if we get here\n\t\t\t\t\t\t\twe know there exists some elements after the mml:mprescript element.\n\n\t\t\t\t\t\t\tThe prescript element has no sub/subsup variation, therefore, even if\n\t\t\t\t\t\t\twe're only writing sub, we need to write out both the sub and sup element.\n\t\t\t\t\t\t\t-->\n                    <xsl:call-template name=\"SplitScripts\">\n                        <xsl:with-param name=\"ndScripts\" select=\"mml:mprescripts[1]/following-sibling::*\"/>\n                    </xsl:call-template>\n                </m:sPre>\n            </xsl:when>\n\n            <!-- Finally, the case with both prescripts and scripts. Create an sPre\n\t\t\t\telement to house the prescripts, with a sub/sup/subsup element at its base. -->\n            <xsl:otherwise>\n                <m:sPre>\n                    <m:e>\n                        <xsl:choose>\n                            <!-- We have both sub and super scripts-->\n                            <xsl:when test=\"$cndSuperScript &gt; 0 and $cndSubScript &gt; 0\">\n                                <m:sSubSup>\n                                    <m:e>\n                                        <xsl:call-template name=\"CreateArgProp\"/>\n                                        <xsl:apply-templates select=\"child::*[1]\"/>\n                                    </m:e>\n\n                                    <!-- scripts come before the mml:mprescript but after the first child, so their\n\t\t\t\t\t\t\t\t positions will be 2, 3, ... ($nndScript + 1) -->\n                                    <xsl:call-template name=\"SplitScripts\">\n                                        <xsl:with-param name=\"ndScripts\"\n                                                        select=\"*[(position() &gt; 1) and (position() &lt;= ($cndScript + 1))]\"/>\n                                    </xsl:call-template>\n                                </m:sSubSup>\n                            </xsl:when>\n                            <!-- Just a sub script -->\n                            <xsl:when test=\"$cndSubScript &gt; 0\">\n                                <m:sSub>\n                                    <m:e>\n                                        <xsl:call-template name=\"CreateArgProp\"/>\n                                        <xsl:apply-templates select=\"child::*[1]\"/>\n                                    </m:e>\n\n                                    <!-- We have prescripts but no super scripts, therefore, do a sub\n\t\t\t\t\t\t\t\t\tand apply templates to all tokens counted by cndScript. -->\n                                    <m:sub>\n                                        <xsl:apply-templates\n                                                select=\"*[position() &gt; 1 and (position() &lt;= ($cndScript + 1))]\"/>\n                                    </m:sub>\n                                </m:sSub>\n                            </xsl:when>\n                            <!-- Just super script -->\n                            <xsl:otherwise>\n                                <m:sSup>\n                                    <m:e>\n                                        <xsl:call-template name=\"CreateArgProp\"/>\n                                        <xsl:apply-templates select=\"child::*[1]\"/>\n                                    </m:e>\n\n                                    <!-- We have prescripts but no sub scripts, therefore, do a sub\n\t\t\t\t\t\t\t\t\tand apply templates to all tokens counted by cndScript. -->\n                                    <m:sup>\n                                        <xsl:apply-templates\n                                                select=\"*[position() &gt; 1 and (position() &lt;= ($cndScript + 1))]\"/>\n                                    </m:sup>\n                                </m:sSup>\n                            </xsl:otherwise>\n                        </xsl:choose>\n                    </m:e>\n\n                    <!-- The prescripts come after the mml:mprescript and if we get here\n\t\t\t\t\t\t\twe know there exists one such element -->\n                    <xsl:call-template name=\"SplitScripts\">\n                        <xsl:with-param name=\"ndScripts\" select=\"mml:mprescripts[1]/following-sibling::*\"/>\n                    </xsl:call-template>\n                </m:sPre>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- Template that determines if ndCur is an equation array.\n\n\t\t\t ndCur is an equation array if:\n\n\t\t\t 1.  There are are no frame lines\n\t\t\t 2.  There are no column lines\n\t\t\t 3.  There are no row lines\n\t\t\t 4.  There is no row with more than 1 column\n\t\t\t 5.  There is no row with fewer than 1 column\n\t\t\t 6.  There are no labeled rows.\n\n\t-->\n    <xsl:template name=\"FIsEqArray\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n\n        <!-- There should be no frame, columnlines, or rowlines -->\n        <xsl:choose>\n            <xsl:when test=\"(not($ndCur/@frame) or $ndCur/@frame='' or $ndCur/@frame='none')\n                      and (not($ndCur/@mml:frame) or $ndCur/@mml:frame='' or $ndCur/@mml:frame='none')\n\t\t\t\t\t\t\t\t      and (not($ndCur/@columnlines) or $ndCur/@columnlines='' or $ndCur/@columnlines='none')\n                      and (not($ndCur/@mml:columnlines) or $ndCur/@mml:columnlines='' or $ndCur/@mml:columnlines='none')\n\t\t\t\t\t\t\t\t      and (not($ndCur/@rowlines) or $ndCur/@rowlines='' or $ndCur/@rowlines='none')\n                      and (not($ndCur/@mml:rowlines) or $ndCur/@mml:rowlines='' or $ndCur/@mml:rowlines='none')\n\t\t\t\t\t\t\t\t      and not($ndCur/mml:mtr[count(mml:mtd) &gt; 1])\n\t\t\t\t\t\t\t\t\t\t\tand not($ndCur/mml:mtr[count(mml:mtd) &lt; 1])\n\t\t\t\t\t\t\t\t      and not($ndCur/mml:mlabeledtr)\">1</xsl:when>\n            <xsl:otherwise>0</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- Template used to determine if we should ignore a collection when iterating through\n\t     a mathml equation array row.\n\n\t\t\t So far, the only thing that needs to be ignored is the argument of an nary.  We\n\t\t\t can ignore this since it is output when we apply-templates to the munder[over]/msub[sup].\n\t-->\n    <xsl:template name=\"FIgnoreCollection\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n\n        <xsl:variable name=\"fNaryArgument\">\n            <xsl:call-template name=\"FIsNaryArgument\">\n                <xsl:with-param name=\"ndCur\" select=\"$ndCur\"/>\n            </xsl:call-template>\n        </xsl:variable>\n\n        <xsl:choose>\n            <xsl:when test=\"$fNaryArgument=1\">1</xsl:when>\n            <xsl:otherwise>0</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- Template used to determine if we've already encountered an maligngroup or malignmark.\n\n\t\t\t This is needed because omml has an implicit spacing alignment (omml spacing alignment =\n\t\t\t mathml's maligngroup element) at the beginning of each equation array row.  Therefore,\n\t\t\t the first maligngroup (implied or explicit) we encounter does not need to be output.\n\t\t\t This template recursively searches up the xml tree and looks at previous siblings to see\n\t\t\t if they have a descendant that is an maligngroup or malignmark.  We look for the malignmark\n\t\t\t to find the implicit maligngroup.\n\t-->\n    <xsl:template name=\"FFirstAlignAlreadyFound\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n\n        <xsl:choose>\n            <xsl:when test=\"count($ndCur/preceding-sibling::*[descendant-or-self::mml:maligngroup\n\t\t\t\t\t\t\t\t                                        or descendant-or-self::mml:malignmark]) &gt; 0\">\n                1\n            </xsl:when>\n            <xsl:when test=\"not($ndCur/parent::mml:mtd)\">\n                <xsl:call-template name=\"FFirstAlignAlreadyFound\">\n                    <xsl:with-param name=\"ndCur\" select=\"$ndCur/parent::*\"/>\n                </xsl:call-template>\n            </xsl:when>\n            <xsl:otherwise>0</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- This template builds a string that is result of concatenating a given string several times.\n\n\t\t\t Given strToRepeat, create a string that has strToRepeat repeated iRepitions times.\n\t-->\n    <xsl:template name=\"ConcatStringRepeat\">\n        <xsl:param name=\"strToRepeat\" select=\"''\"/>\n        <xsl:param name=\"iRepetitions\" select=\"0\"/>\n        <xsl:param name=\"strBuilding\" select=\"''\"/>\n\n        <xsl:choose>\n            <xsl:when test=\"$iRepetitions &lt;= 0\">\n                <xsl:value-of select=\"$strBuilding\"/>\n            </xsl:when>\n            <xsl:otherwise>\n                <xsl:call-template name=\"ConcatStringRepeat\">\n                    <xsl:with-param name=\"strToRepeat\" select=\"$strToRepeat\"/>\n                    <xsl:with-param name=\"iRepetitions\" select=\"$iRepetitions - 1\"/>\n                    <xsl:with-param name=\"strBuilding\" select=\"concat($strBuilding, $strToRepeat)\"/>\n                </xsl:call-template>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- This template determines if ndCur is a special collection.\n\t\t\t By special collection, I mean is ndCur the outer element of some special grouping\n\t\t\t of mathml elements that actually represents some over all omml structure.\n\n\t\t\t For instance, is ndCur a linear fraction, or an omml function.\n\t-->\n    <xsl:template name=\"FSpecialCollection\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n        <xsl:choose>\n            <xsl:when test=\"$ndCur/self::mml:mrow\">\n                <xsl:variable name=\"fLinearFraction\">\n                    <xsl:call-template name=\"FLinearFrac\">\n                        <xsl:with-param name=\"ndCur\" select=\"$ndCur\"/>\n                    </xsl:call-template>\n                </xsl:variable>\n                <xsl:variable name=\"fFunc\">\n                    <xsl:call-template name=\"FIsFunc\">\n                        <xsl:with-param name=\"ndCur\" select=\"$ndCur\"/>\n                    </xsl:call-template>\n                </xsl:variable>\n                <xsl:choose>\n                    <xsl:when test=\"$fLinearFraction=1 or $fFunc=1\">1</xsl:when>\n                    <xsl:otherwise>0</xsl:otherwise>\n                </xsl:choose>\n            </xsl:when>\n            <xsl:otherwise>0</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <!-- This template iterates through the children of an equation array row (mtr) and outputs\n\t     the equation.\n\n\t\t\t This template does all the work to output ampersands and skip the right elements when needed.\n\t-->\n    <xsl:template name=\"ProcessEqArrayRow\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n\n        <xsl:for-each select=\"$ndCur/*\">\n            <xsl:variable name=\"fSpecialCollection\">\n                <xsl:call-template name=\"FSpecialCollection\">\n                    <xsl:with-param name=\"ndCur\" select=\".\"/>\n                </xsl:call-template>\n            </xsl:variable>\n            <xsl:variable name=\"fIgnoreCollection\">\n                <xsl:call-template name=\"FIgnoreCollection\">\n                    <xsl:with-param name=\"ndCur\" select=\".\"/>\n                </xsl:call-template>\n            </xsl:variable>\n            <xsl:choose>\n                <!-- If we have an alignment element output the ampersand. -->\n                <xsl:when test=\"self::mml:maligngroup or self::mml:malignmark\">\n                    <!-- Omml has an implied spacing alignment at the beginning of each equation.\n\t\t\t\t\t     Therefore, if this is the first ampersand to be output, don't actually output. -->\n                    <xsl:variable name=\"fFirstAlignAlreadyFound\">\n                        <xsl:call-template name=\"FFirstAlignAlreadyFound\">\n                            <xsl:with-param name=\"ndCur\" select=\".\"/>\n                        </xsl:call-template>\n                    </xsl:variable>\n                    <!-- Don't output unless it is an malignmark or we have already previously found an alignment point. -->\n                    <xsl:if test=\"self::mml:malignmark or $fFirstAlignAlreadyFound=1\">\n                        <m:r>\n                            <m:t>&amp;</m:t>\n                        </m:r>\n                    </xsl:if>\n                </xsl:when>\n                <!-- If this node is an non-special mrow or mstyle and we aren't supposed to ignore this collection, then\n\t\t\t\t     go ahead an apply templates to this node. -->\n                <xsl:when\n                        test=\"$fIgnoreCollection=0 and ((self::mml:mrow and $fSpecialCollection=0) or self::mml:mstyle)\">\n                    <xsl:call-template name=\"ProcessEqArrayRow\">\n                        <xsl:with-param name=\"ndCur\" select=\".\"/>\n                    </xsl:call-template>\n                </xsl:when>\n                <!-- At this point we have some mathml structure (fraction, nary, non-grouping element, etc.) -->\n                <!-- If this mathml structure has alignment groups or marks as children, then extract those since\n\t\t\t\t     omml can't handle that. -->\n                <xsl:when test=\"descendant::mml:maligngroup or descendant::mml:malignmark\">\n                    <xsl:variable name=\"cMalignGroups\">\n                        <xsl:value-of select=\"count(descendant::mml:maligngroup)\"/>\n                    </xsl:variable>\n                    <xsl:variable name=\"cMalignMarks\">\n                        <xsl:value-of select=\"count(descendant::mml:malignmark)\"/>\n                    </xsl:variable>\n                    <!-- Output all maligngroups and malignmarks as '&' -->\n                    <xsl:if test=\"$cMalignGroups + $cMalignMarks &gt; 0\">\n                        <xsl:variable name=\"str\">\n                            <xsl:call-template name=\"ConcatStringRepeat\">\n                                <xsl:with-param name=\"strToRepeat\" select=\"'&amp;'\"/>\n                                <xsl:with-param name=\"iRepetitions\" select=\"$cMalignGroups + $cMalignMarks\"/>\n                                <xsl:with-param name=\"strBuilding\" select=\"''\"/>\n                            </xsl:call-template>\n                        </xsl:variable>\n                        <xsl:element name=\"m:r\">\n                            <xsl:element name=\"m:t\">\n                                <xsl:call-template name=\"OutputText\">\n                                    <xsl:with-param name=\"sInput\" select=\"$str\"/>\n                                </xsl:call-template>\n                            </xsl:element>\n                        </xsl:element>\n                    </xsl:if>\n                    <!-- Now that the '&' have been extracted, just apply-templates to this node.-->\n                    <xsl:apply-templates select=\".\"/>\n                </xsl:when>\n                <!-- If there are no alignment points as descendants, then go ahead and output this node. -->\n                <xsl:otherwise>\n                    <xsl:apply-templates select=\".\"/>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:for-each>\n    </xsl:template>\n\n    <!-- This template transforms mtable into its appropriate omml type.\n\n\t\t\t There are two possible omml constructs that an mtable can become:  a matrix or\n\t\t\t an equation array.\n\n\t\t\t Because omml has no generic table construct, the omml matrix is the best approximate\n\t\t\t for a mathml table.\n\n\t\t\t Our equation array transformation is very simple.  The main goal of this transform is to\n\t\t\t allow roundtripping omml eq arrays through mathml.  The template ProcessEqArrayRow was never\n\t\t\t intended to account for many of the alignment flexibilities that are present in mathml like\n\t\t\t using the alig attribute, using alignmark attribute in token elements, etc.\n\n\t\t\t The restrictions on this transform require <malignmark> and <maligngroup> elements to be outside of\n\t\t\t any non-grouping mathml elements (that is, mrow and mstyle).  Moreover, these elements cannot be the children of\n\t\t\t mrows that represent linear fractions or functions.  Also, <malignmark> cannot be a child\n\t\t\t of token attributes.\n\n\t\t\t In the case that the above\n\n\t-->\n    <xsl:template match=\"mml:mtable\">\n        <xsl:variable name=\"fEqArray\">\n            <xsl:call-template name=\"FIsEqArray\">\n                <xsl:with-param name=\"ndCur\" select=\".\"/>\n            </xsl:call-template>\n        </xsl:variable>\n        <xsl:choose>\n            <xsl:when test=\"$fEqArray=1\">\n                <xsl:element name=\"m:eqArr\">\n                    <xsl:for-each select=\"mml:mtr\">\n                        <xsl:element name=\"m:e\">\n                            <xsl:call-template name=\"ProcessEqArrayRow\">\n                                <xsl:with-param name=\"ndCur\" select=\"mml:mtd\"/>\n                            </xsl:call-template>\n                        </xsl:element>\n                    </xsl:for-each>\n                </xsl:element>\n            </xsl:when>\n            <xsl:otherwise>\n                <xsl:variable name=\"cMaxElmtsInRow\">\n                    <xsl:call-template name=\"CountMaxElmtsInRow\">\n                        <xsl:with-param name=\"ndCur\" select=\"*[1]\"/>\n                        <xsl:with-param name=\"cMaxElmtsInRow\" select=\"0\"/>\n                    </xsl:call-template>\n                </xsl:variable>\n                <m:m>\n                    <m:mPr>\n                        <m:baseJc m:val=\"center\"/>\n                        <m:plcHide m:val=\"on\"/>\n                        <m:mcs>\n                            <m:mc>\n                                <m:mcPr>\n                                    <m:count>\n                                        <xsl:attribute name=\"m:val\">\n                                            <xsl:value-of select=\"$cMaxElmtsInRow\"/>\n                                        </xsl:attribute>\n                                    </m:count>\n                                    <m:mcJc m:val=\"center\"/>\n                                </m:mcPr>\n                            </m:mc>\n                        </m:mcs>\n                    </m:mPr>\n                    <xsl:for-each select=\"*\">\n                        <xsl:choose>\n                            <xsl:when test=\"self::mml:mtr or self::mml:mlabeledtr\">\n                                <m:mr>\n                                    <xsl:choose>\n                                        <xsl:when test=\"self::mml:mtr\">\n                                            <xsl:for-each select=\"*\">\n                                                <m:e>\n                                                    <xsl:apply-templates select=\".\"/>\n                                                </m:e>\n                                            </xsl:for-each>\n                                            <xsl:call-template name=\"CreateEmptyElmt\">\n                                                <xsl:with-param name=\"cEmptyMtd\" select=\"$cMaxElmtsInRow - count(*)\"/>\n                                            </xsl:call-template>\n                                        </xsl:when>\n                                        <xsl:otherwise>\n                                            <xsl:for-each select=\"*[position() &gt; 1]\">\n                                                <m:e>\n                                                    <xsl:apply-templates select=\".\"/>\n                                                </m:e>\n                                            </xsl:for-each>\n                                            <xsl:call-template name=\"CreateEmptyElmt\">\n                                                <xsl:with-param name=\"cEmptyMtd\"\n                                                                select=\"$cMaxElmtsInRow - (count(*) - 1)\"/>\n                                            </xsl:call-template>\n                                        </xsl:otherwise>\n                                    </xsl:choose>\n                                </m:mr>\n                            </xsl:when>\n                            <xsl:otherwise>\n                                <m:mr>\n                                    <m:e>\n                                        <xsl:apply-templates select=\".\"/>\n                                    </m:e>\n                                    <xsl:call-template name=\"CreateEmptyElmt\">\n                                        <xsl:with-param name=\"cEmptyMtd\" select=\"$cMaxElmtsInRow - 1\"/>\n                                    </xsl:call-template>\n                                </m:mr>\n                            </xsl:otherwise>\n                        </xsl:choose>\n                    </xsl:for-each>\n                </m:m>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n    <xsl:template match=\"m:mtd\">\n        <xsl:apply-templates select=\"*\"/>\n    </xsl:template>\n    <xsl:template name=\"CreateEmptyElmt\">\n        <xsl:param name=\"cEmptyMtd\"/>\n        <xsl:if test=\"$cEmptyMtd &gt; 0\">\n            <m:e></m:e>\n            <xsl:call-template name=\"CreateEmptyElmt\">\n                <xsl:with-param name=\"cEmptyMtd\" select=\"$cEmptyMtd - 1\"/>\n            </xsl:call-template>\n        </xsl:if>\n    </xsl:template>\n    <xsl:template name=\"CountMaxElmtsInRow\">\n        <xsl:param name=\"ndCur\"/>\n        <xsl:param name=\"cMaxElmtsInRow\" select=\"0\"/>\n        <xsl:choose>\n            <xsl:when test=\"not($ndCur)\">\n                <xsl:value-of select=\"$cMaxElmtsInRow\"/>\n            </xsl:when>\n            <xsl:otherwise>\n                <xsl:call-template name=\"CountMaxElmtsInRow\">\n                    <xsl:with-param name=\"ndCur\" select=\"$ndCur/following-sibling::*[1]\"/>\n                    <xsl:with-param name=\"cMaxElmtsInRow\">\n                        <xsl:choose>\n                            <xsl:when test=\"local-name($ndCur) = 'mlabeledtr' and\n\t\t\t\t\t\t\t\t            namespace-uri($ndCur) = 'http://www.w3.org/1998/Math/MathML'\">\n                                <xsl:choose>\n                                    <xsl:when test=\"(count($ndCur/*) - 1) &gt; $cMaxElmtsInRow\">\n                                        <xsl:value-of select=\"count($ndCur/*) - 1\"/>\n                                    </xsl:when>\n                                    <xsl:otherwise>\n                                        <xsl:value-of select=\"$cMaxElmtsInRow\"/>\n                                    </xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:when>\n                            <xsl:when test=\"local-name($ndCur) = 'mtr' and\n\t\t\t\t\t\t\t\t            namespace-uri($ndCur) = 'http://www.w3.org/1998/Math/MathML'\">\n                                <xsl:choose>\n                                    <xsl:when test=\"count($ndCur/*) &gt; $cMaxElmtsInRow\">\n                                        <xsl:value-of select=\"count($ndCur/*)\"/>\n                                    </xsl:when>\n                                    <xsl:otherwise>\n                                        <xsl:value-of select=\"$cMaxElmtsInRow\"/>\n                                    </xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:when>\n                            <xsl:otherwise>\n                                <xsl:choose>\n                                    <xsl:when test=\"1 &gt; $cMaxElmtsInRow\">\n                                        <xsl:value-of select=\"1\"/>\n                                    </xsl:when>\n                                    <xsl:otherwise>\n                                        <xsl:value-of select=\"$cMaxElmtsInRow\"/>\n                                    </xsl:otherwise>\n                                </xsl:choose>\n                            </xsl:otherwise>\n                        </xsl:choose>\n                    </xsl:with-param>\n                </xsl:call-template>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <xsl:template name=\"GetMglyphAltText\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n        <xsl:choose>\n            <xsl:when test=\"$ndCur/@alt\">\n                <xsl:value-of select=\"normalize-space($ndCur/@alt)\"/>\n            </xsl:when>\n            <xsl:otherwise>\n                <xsl:value-of select=\"normalize-space($ndCur/@mml:alt)\"/>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <xsl:template match=\"mml:mglyph\">\n        <xsl:element name=\"m:r\">\n            <xsl:element name=\"m:rPr\">\n                <xsl:element name=\"m:nor\"/>\n            </xsl:element>\n            <xsl:element name=\"m:t\">\n                <xsl:call-template name=\"OutputText\">\n                    <xsl:with-param name=\"sInput\">\n                        <xsl:call-template name=\"GetMglyphAltText\">\n                            <xsl:with-param name=\"ndCur\" select=\".\"/>\n                        </xsl:call-template>\n                    </xsl:with-param>\n                </xsl:call-template>\n            </xsl:element>\n        </xsl:element>\n    </xsl:template>\n\n    <!-- Omml doesn't really support mglyph, so just output the alt text -->\n    <xsl:template match=\"mml:mi[child::mml:mglyph] |\n\t                     mml:mn[child::mml:mglyph] |\n\t                     mml:mo[child::mml:mglyph] |\n\t                     mml:ms[child::mml:mglyph] |\n\t                     mml:mtext[child::mml:mglyph]\">\n        <xsl:variable name=\"mathvariant\">\n            <xsl:choose>\n                <xsl:when test=\"@mathvariant\">\n                    <xsl:value-of select=\"@mathvariant\"/>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:value-of select=\"@mml:mathvariant\"/>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n        <xsl:variable name=\"fontstyle\">\n            <xsl:choose>\n                <xsl:when test=\"@fontstyle\">\n                    <xsl:value-of select=\"@fontstyle\"/>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:value-of select=\"@mml:fontstyle\"/>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n        <xsl:variable name=\"fontweight\">\n            <xsl:choose>\n                <xsl:when test=\"@fontweight\">\n                    <xsl:value-of select=\"@fontweight\"/>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:value-of select=\"@mml:fontweight\"/>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n        <xsl:variable name=\"mathcolor\">\n            <xsl:choose>\n                <xsl:when test=\"@mathcolor\">\n                    <xsl:value-of select=\"@mathcolor\"/>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:value-of select=\"@mml:mathcolor\"/>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n        <xsl:variable name=\"mathsize\">\n            <xsl:choose>\n                <xsl:when test=\"@mathsize\">\n                    <xsl:value-of select=\"@mathsize\"/>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:value-of select=\"@mml:mathsize\"/>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n        <xsl:variable name=\"color\">\n            <xsl:choose>\n                <xsl:when test=\"@color\">\n                    <xsl:value-of select=\"@color\"/>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:value-of select=\"@mml:color\"/>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n        <xsl:variable name=\"fontsize\">\n            <xsl:choose>\n                <xsl:when test=\"@fontsize\">\n                    <xsl:value-of select=\"@fontsize\"/>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:value-of select=\"@mml:fontsize\"/>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n        <xsl:variable name=\"fNor\">\n            <xsl:call-template name=\"FNor\">\n                <xsl:with-param name=\"ndCur\" select=\".\"/>\n            </xsl:call-template>\n        </xsl:variable>\n\n        <!-- Output MS Left Quote (if need be) -->\n        <xsl:if test=\"self::mml:ms\">\n            <xsl:variable name=\"chLquote\">\n                <xsl:call-template name=\"LQuoteFromMs\">\n                    <xsl:with-param name=\"curMs\" select=\".\"/>\n                </xsl:call-template>\n            </xsl:variable>\n            <xsl:element name=\"m:r\">\n                <xsl:call-template name=\"CreateRunProp\">\n                    <xsl:with-param name=\"mathvariant\" select=\"$mathvariant\"/>\n                    <xsl:with-param name=\"fontstyle\" select=\"$fontstyle\"/>\n                    <xsl:with-param name=\"fontweight\" select=\"$fontweight\"/>\n                    <xsl:with-param name=\"mathcolor\" select=\"$mathcolor\"/>\n                    <xsl:with-param name=\"mathsize\" select=\"$mathsize\"/>\n                    <xsl:with-param name=\"color\" select=\"$color\"/>\n                    <xsl:with-param name=\"fontsize\" select=\"$fontsize\"/>\n                    <xsl:with-param name=\"fNor\" select=\"$fNor\"/>\n                    <xsl:with-param name=\"ndCur\" select=\".\"/>\n                </xsl:call-template>\n                <xsl:element name=\"m:t\">\n                    <xsl:call-template name=\"OutputText\">\n                        <xsl:with-param name=\"sInput\" select=\"$chLquote\"/>\n                    </xsl:call-template>\n                </xsl:element>\n            </xsl:element>\n        </xsl:if>\n        <xsl:for-each select=\"mml:mglyph | text()\">\n            <xsl:variable name=\"fForceNor\">\n                <xsl:choose>\n                    <xsl:when test=\"self::mml:mglyph\">1</xsl:when>\n                    <xsl:otherwise>0</xsl:otherwise>\n                </xsl:choose>\n            </xsl:variable>\n\n            <xsl:variable name=\"str\">\n                <xsl:choose>\n                    <xsl:when test=\"self::mml:mglyph\">\n                        <xsl:call-template name=\"GetMglyphAltText\">\n                            <xsl:with-param name=\"ndCur\" select=\".\"/>\n                        </xsl:call-template>\n                    </xsl:when>\n                    <xsl:otherwise>\n                        <xsl:value-of select=\"normalize-space(.)\"/>\n                    </xsl:otherwise>\n                </xsl:choose>\n            </xsl:variable>\n            <xsl:if test=\"string-length($str) &gt; 0\">\n                <xsl:element name=\"m:r\">\n                    <xsl:call-template name=\"CreateRunProp\">\n                        <xsl:with-param name=\"mathvariant\" select=\"$mathvariant\"/>\n                        <xsl:with-param name=\"fontstyle\" select=\"$fontstyle\"/>\n                        <xsl:with-param name=\"fontweight\" select=\"$fontweight\"/>\n                        <xsl:with-param name=\"mathcolor\" select=\"$mathcolor\"/>\n                        <xsl:with-param name=\"mathsize\" select=\"$mathsize\"/>\n                        <xsl:with-param name=\"color\" select=\"$color\"/>\n                        <xsl:with-param name=\"fontsize\" select=\"$fontsize\"/>\n                        <xsl:with-param name=\"fNor\">\n                            <xsl:choose>\n                                <xsl:when test=\"$fForceNor=1\">1</xsl:when>\n                                <xsl:otherwise>\n                                    <xsl:value-of select=\"$fNor\"/>\n                                </xsl:otherwise>\n                            </xsl:choose>\n                        </xsl:with-param>\n                        <xsl:with-param name=\"ndCur\" select=\".\"/>\n                    </xsl:call-template>\n                    <xsl:element name=\"m:t\">\n                        <xsl:call-template name=\"OutputText\">\n                            <xsl:with-param name=\"sInput\" select=\"$str\"/>\n                        </xsl:call-template>\n                    </xsl:element>\n                </xsl:element>\n            </xsl:if>\n        </xsl:for-each>\n\n        <!-- Output MS Right Quote (if need be) -->\n        <xsl:if test=\"self::mml:ms\">\n            <xsl:variable name=\"chRquote\">\n                <xsl:call-template name=\"RQuoteFromMs\">\n                    <xsl:with-param name=\"curMs\" select=\".\"/>\n                </xsl:call-template>\n            </xsl:variable>\n            <xsl:element name=\"m:r\">\n                <xsl:call-template name=\"CreateRunProp\">\n                    <xsl:with-param name=\"mathvariant\" select=\"$mathvariant\"/>\n                    <xsl:with-param name=\"fontstyle\" select=\"$fontstyle\"/>\n                    <xsl:with-param name=\"fontweight\" select=\"$fontweight\"/>\n                    <xsl:with-param name=\"mathcolor\" select=\"$mathcolor\"/>\n                    <xsl:with-param name=\"mathsize\" select=\"$mathsize\"/>\n                    <xsl:with-param name=\"color\" select=\"$color\"/>\n                    <xsl:with-param name=\"fontsize\" select=\"$fontsize\"/>\n                    <xsl:with-param name=\"fNor\" select=\"$fNor\"/>\n                    <xsl:with-param name=\"ndCur\" select=\".\"/>\n                </xsl:call-template>\n                <xsl:element name=\"m:t\">\n                    <xsl:call-template name=\"OutputText\">\n                        <xsl:with-param name=\"sInput\" select=\"$chRquote\"/>\n                    </xsl:call-template>\n                </xsl:element>\n            </xsl:element>\n        </xsl:if>\n    </xsl:template>\n\n    <xsl:template name=\"FStrContainsNonZeroDigit\">\n        <xsl:param name=\"s\"/>\n\n        <!-- Translate any nonzero digit into a 9 -->\n        <xsl:variable name=\"sNonZeroDigitsToNineDigit\" select=\"translate($s, '12345678', '99999999')\"/>\n        <xsl:choose>\n            <!-- Search for 9s -->\n            <xsl:when test=\"contains($sNonZeroDigitsToNineDigit, '9')\">1</xsl:when>\n            <xsl:otherwise>0</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <xsl:template name=\"FStrContainsDigits\">\n        <xsl:param name=\"s\"/>\n\n        <!-- Translate any digit into a 0 -->\n        <xsl:variable name=\"sDigitsToZeroDigit\" select=\"translate($s, '123456789', '000000000')\"/>\n        <xsl:choose>\n            <!-- Search for 0s -->\n            <xsl:when test=\"contains($sDigitsToZeroDigit, '0')\">1</xsl:when>\n            <xsl:otherwise>0</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n\n    <!-- Used to determine if mpadded attribute {width, height, depth }\n       indicates to show everything.\n\n       Unlike mathml, whose mpadded structure has great flexibility in modifying the\n       bounding box's width, height, and depth, Word can only have zero or full width, height, and depth.\n       Thus, if the width, height, or depth attributes indicate any kind of nonzero width, height,\n       or depth, we'll translate that into a show full width, height, or depth for OMML.  Only if the attribute\n       indicates a zero width, height, or depth, will we report back FFull as false.\n\n       Example:  s=0%    ->  FFull returns 0.\n                 s=2%    ->  FFull returns 1.\n                 s=0.1em ->  FFull returns 1.\n\n       -->\n    <xsl:template name=\"FFull\">\n        <xsl:param name=\"s\"/>\n\n        <xsl:variable name=\"fStrContainsNonZeroDigit\">\n            <xsl:call-template name=\"FStrContainsNonZeroDigit\">\n                <xsl:with-param name=\"s\" select=\"$s\"/>\n            </xsl:call-template>\n        </xsl:variable>\n\n        <xsl:variable name=\"fStrContainsDigits\">\n            <xsl:call-template name=\"FStrContainsDigits\">\n                <xsl:with-param name=\"s\" select=\"$s\"/>\n            </xsl:call-template>\n        </xsl:variable>\n\n        <xsl:choose>\n            <!-- String contained non-zero digit -->\n            <xsl:when test=\"$fStrContainsNonZeroDigit=1\">1</xsl:when>\n            <!-- String didn't contain a non-zero digit, but it did contain digits.\n           This must mean that all digits in the string were 0s. -->\n            <xsl:when test=\"$fStrContainsDigits=1\">0</xsl:when>\n            <!-- Else, no digits, therefore, return true.\n           We return true in the otherwise condition to take account for the possibility\n           in MathML to say something like width=\"height\". -->\n            <xsl:otherwise>1</xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n\n    <!-- Just outputs phant properties, doesn't do any fancy\n       thinking of its own, just obeys the defaults of\n       phants. -->\n    <xsl:template name=\"CreatePhantPropertiesCore\">\n        <xsl:param name=\"fShow\" select=\"1\"/>\n        <xsl:param name=\"fFullWidth\" select=\"1\"/>\n        <xsl:param name=\"fFullHeight\" select=\"1\"/>\n        <xsl:param name=\"fFullDepth\" select=\"1\"/>\n\n        <xsl:if test=\"$fShow=0\n                    or $fFullWidth=0\n                    or $fFullHeight=0\n                    or $fFullDepth=0\">\n            <xsl:element name=\"m:phantPr\">\n                <xsl:if test=\"$fShow=0\">\n                    <xsl:element name=\"m:show\">\n                        <xsl:attribute name=\"m:val\">off</xsl:attribute>\n                    </xsl:element>\n                </xsl:if>\n                <xsl:if test=\"$fFullWidth=0\">\n                    <xsl:element name=\"m:zeroWid\">\n                        <xsl:attribute name=\"m:val\">on</xsl:attribute>\n                    </xsl:element>\n                </xsl:if>\n                <xsl:if test=\"$fFullHeight=0\">\n                    <xsl:element name=\"m:zeroAsc\">\n                        <xsl:attribute name=\"m:val\">on</xsl:attribute>\n                    </xsl:element>\n                </xsl:if>\n                <xsl:if test=\"$fFullDepth=0\">\n                    <xsl:element name=\"m:zeroDesc\">\n                        <xsl:attribute name=\"m:val\">on</xsl:attribute>\n                    </xsl:element>\n                </xsl:if>\n            </xsl:element>\n        </xsl:if>\n    </xsl:template>\n\n    <!-- Figures out if we should factor in width, height, and depth attributes.\n\n       If so, then it\n       gets these attributes, does some processing to figure out what the attributes indicate,\n       then passes these indications to CreatePhantPropertiesCore.\n\n       If we aren't supposed to factor in width, height, or depth, then we'll just output the show\n       attribute. -->\n    <xsl:template name=\"CreatePhantProperties\">\n        <xsl:param name=\"ndCur\" select=\".\"/>\n        <xsl:param name=\"fShow\" select=\"1\"/>\n\n        <xsl:choose>\n            <!-- In the special case that we have an mphantom with one child which is an mpadded, then we should\n           subsume the mpadded attributes into the mphantom attributes.  The test statement below imples the\n           'one child which is an mpadded'.  The first part, that the parent of mpadded is an mphantom, is implied\n           by being in this template, which is only called when we've encountered an mphantom.\n\n           Word outputs its invisible phantoms with smashing as\n\n              <mml:mphantom>\n                <mml:mpadded . . . >\n\n                </mml:mpadded>\n              </mml:mphantom>\n\n            This test is used to allow roundtripping smashed invisible phantoms. -->\n            <xsl:when test=\"count($ndCur/child::*)=1 and count($ndCur/mml:mpadded)=1\">\n                <xsl:variable name=\"sLowerCaseWidth\">\n                    <xsl:choose>\n                        <xsl:when test=\"$ndCur/mml:mpadded/@width\">\n                            <xsl:value-of\n                                    select=\"translate($ndCur/mml:mpadded/@width, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of\n                                    select=\"translate($ndCur/mml:mpadded/@mml:width, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:variable>\n                <xsl:variable name=\"sLowerCaseHeight\">\n                    <xsl:choose>\n                        <xsl:when test=\"$ndCur/mml:mpadded/@height\">\n                            <xsl:value-of\n                                    select=\"translate($ndCur/mml:mpadded/@height, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of\n                                    select=\"translate($ndCur/mml:mpadded/@mml:height, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:variable>\n                <xsl:variable name=\"sLowerCaseDepth\">\n                    <xsl:choose>\n                        <xsl:when test=\"$ndCur/mml:mpadded/@depth\">\n                            <xsl:value-of\n                                    select=\"translate($ndCur/mml:mpadded/@depth, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of\n                                    select=\"translate($ndCur/mml:mpadded/@mml:depth, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:variable>\n\n                <xsl:variable name=\"fFullWidth\">\n                    <xsl:call-template name=\"FFull\">\n                        <xsl:with-param name=\"s\" select=\"$sLowerCaseWidth\"/>\n                    </xsl:call-template>\n                </xsl:variable>\n                <xsl:variable name=\"fFullHeight\">\n                    <xsl:call-template name=\"FFull\">\n                        <xsl:with-param name=\"s\" select=\"$sLowerCaseHeight\"/>\n                    </xsl:call-template>\n                </xsl:variable>\n                <xsl:variable name=\"fFullDepth\">\n                    <xsl:call-template name=\"FFull\">\n                        <xsl:with-param name=\"s\" select=\"$sLowerCaseDepth\"/>\n                    </xsl:call-template>\n                </xsl:variable>\n\n                <xsl:call-template name=\"CreatePhantPropertiesCore\">\n                    <xsl:with-param name=\"fShow\" select=\"$fShow\"/>\n                    <xsl:with-param name=\"fFullWidth\" select=\"$fFullWidth\"/>\n                    <xsl:with-param name=\"fFullHeight\" select=\"$fFullHeight\"/>\n                    <xsl:with-param name=\"fFullDepth\" select=\"$fFullDepth\"/>\n                </xsl:call-template>\n            </xsl:when>\n            <xsl:otherwise>\n                <xsl:call-template name=\"CreatePhantPropertiesCore\">\n                    <xsl:with-param name=\"fShow\" select=\"$fShow\"/>\n                </xsl:call-template>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <xsl:template match=\"mml:mpadded\">\n        <xsl:choose>\n            <xsl:when\n                    test=\"count(parent::mml:mphantom)=1 and count(preceding-sibling::*)=0 and count(following-sibling::*)=0\">\n                <!-- This mpadded is inside an mphantom that has already setup phantom attributes, therefore, just apply templates -->\n                <xsl:apply-templates select=\"*\"/>\n            </xsl:when>\n            <xsl:otherwise>\n                <xsl:variable name=\"sLowerCaseWidth\">\n                    <xsl:choose>\n                        <xsl:when test=\"@width\">\n                            <xsl:value-of select=\"@width\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"@mml:width\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:variable>\n                <xsl:variable name=\"sLowerCaseHeight\">\n                    <xsl:choose>\n                        <xsl:when test=\"@height\">\n                            <xsl:value-of select=\"@height\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"@mml:height\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:variable>\n                <xsl:variable name=\"sLowerCaseDepth\">\n                    <xsl:choose>\n                        <xsl:when test=\"@depth\">\n                            <xsl:value-of select=\"@depth\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of select=\"@mml:depth\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:variable>\n\n                <xsl:variable name=\"fFullWidth\">\n                    <xsl:call-template name=\"FFull\">\n                        <xsl:with-param name=\"s\" select=\"$sLowerCaseWidth\"/>\n                    </xsl:call-template>\n                </xsl:variable>\n                <xsl:variable name=\"fFullHeight\">\n                    <xsl:call-template name=\"FFull\">\n                        <xsl:with-param name=\"s\" select=\"$sLowerCaseHeight\"/>\n                    </xsl:call-template>\n                </xsl:variable>\n                <xsl:variable name=\"fFullDepth\">\n                    <xsl:call-template name=\"FFull\">\n                        <xsl:with-param name=\"s\" select=\"$sLowerCaseDepth\"/>\n                    </xsl:call-template>\n                </xsl:variable>\n\n                <xsl:element name=\"m:phant\">\n                    <xsl:call-template name=\"CreatePhantPropertiesCore\">\n                        <xsl:with-param name=\"fShow\" select=\"1\"/>\n                        <xsl:with-param name=\"fFullWidth\" select=\"$fFullWidth\"/>\n                        <xsl:with-param name=\"fFullHeight\" select=\"$fFullHeight\"/>\n                        <xsl:with-param name=\"fFullDepth\" select=\"$fFullDepth\"/>\n                    </xsl:call-template>\n                    <m:e>\n                        <xsl:apply-templates select=\"*\"/>\n                    </m:e>\n                </xsl:element>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <xsl:template match=\"mml:mphantom\">\n        <xsl:element name=\"m:phant\">\n            <xsl:call-template name=\"CreatePhantProperties\">\n                <xsl:with-param name=\"ndCur\" select=\".\"/>\n                <xsl:with-param name=\"fShow\" select=\"0\"/>\n            </xsl:call-template>\n            <m:e>\n                <xsl:apply-templates select=\"*\"/>\n            </m:e>\n        </xsl:element>\n    </xsl:template>\n\n    <xsl:template name=\"isNaryOper\">\n        <xsl:param name=\"sNdCur\"/>\n        <xsl:value-of select=\"($sNdCur = '&#x222B;'\n                            or $sNdCur = '&#x222C;'\n                            or $sNdCur = '&#x222D;'\n                            or $sNdCur = '&#x222E;'\n                            or $sNdCur = '&#x222F;'\n                            or $sNdCur = '&#x2230;'\n                            or $sNdCur = '&#x2232;'\n                            or $sNdCur = '&#x2233;'\n                            or $sNdCur = '&#x2231;'\n                            or $sNdCur = '&#x2229;'\n                            or $sNdCur = '&#x222A;'\n                            or $sNdCur = '&#x220F;'\n                            or $sNdCur = '&#x2210;'\n                            or $sNdCur = '&#x2211;'\n                            or $sNdCur = '&#x22C0;'\n                            or $sNdCur = '&#x22C1;'\n                            or $sNdCur = '&#x22C2;'\n                            or $sNdCur = '&#x22C3;')\"/>\n    </xsl:template>\n\n\n    <xsl:template name=\"isNary\">\n        <!-- ndCur is the element around the nAry operator -->\n        <xsl:param name=\"ndCur\"/>\n        <xsl:variable name=\"sNdCur\">\n            <xsl:value-of select=\"normalize-space($ndCur)\"/>\n        </xsl:variable>\n\n        <xsl:variable name=\"fNaryOper\">\n            <xsl:call-template name=\"isNaryOper\">\n                <xsl:with-param name=\"sNdCur\" select=\"$sNdCur\"/>\n            </xsl:call-template>\n        </xsl:variable>\n\n        <!-- Narys shouldn't be MathML accents.  -->\n        <xsl:variable name=\"fUnder\">\n            <xsl:choose>\n                <xsl:when test=\"$ndCur/parent::*[self::mml:munder]\">1</xsl:when>\n                <xsl:otherwise>0</xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n\n        <xsl:variable name=\"sLowerCaseAccent\">\n            <xsl:choose>\n                <xsl:when test=\"$fUnder=1\">\n                    <xsl:choose>\n                        <xsl:when test=\"$ndCur/parent::*[self::mml:munder]/@accentunder\">\n                            <xsl:value-of\n                                    select=\"translate($ndCur/parent::*[self::mml:munder]/@accentunder, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of\n                                    select=\"translate($ndCur/parent::*[self::mml:munder]/@mml:accentunder, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:choose>\n                        <xsl:when test=\"$ndCur/parent::*/@accent\">\n                            <xsl:value-of select=\"translate($ndCur/parent::*/@accent, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:value-of\n                                    select=\"translate($ndCur/parent::*/@mml:accent, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n\n        <xsl:variable name=\"fAccent\">\n            <xsl:choose>\n                <xsl:when test=\"$sLowerCaseAccent='true'\">1</xsl:when>\n                <xsl:otherwise>0</xsl:otherwise>\n            </xsl:choose>\n        </xsl:variable>\n\n        <xsl:choose>\n            <!-- This ndCur is in fact part of an nAry if\n\n           1)  The last descendant of ndCur (which could be ndCur itself) is an operator.\n           2)  Along that chain of descendants we only encounter mml:mo, mml:mstyle, and mml:mrow elements.\n           3)  the operator in mml:mo is a valid nAry operator\n           4)  The nAry is not accented.\n           -->\n            <xsl:when test=\"$fNaryOper = 'true'\n                      and $fAccent=0\n                      and $ndCur/descendant-or-self::*[last()]/self::mml:mo\n                      and not($ndCur/descendant-or-self::*[not(self::mml:mo or\n\t\t\t                                                     self::mml:mstyle or\n\t\t\t                                                     self::mml:mrow)])\">\n                <xsl:value-of select=\"true()\"/>\n            </xsl:when>\n            <xsl:otherwise>\n                <xsl:value-of select=\"false()\"/>\n            </xsl:otherwise>\n        </xsl:choose>\n    </xsl:template>\n\n    <xsl:template name=\"CreateNaryProp\">\n        <xsl:param name=\"chr\"/>\n        <xsl:param name=\"sMathmlType\"/>\n        <xsl:param name=\"sGrow\">\n            <xsl:choose>\n                <xsl:when test=\"child::*[1]/@stretchy\">\n                    <xsl:value-of select=\"translate(child::*[1]/@stretchy, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                </xsl:when>\n                <xsl:otherwise>\n                    <xsl:value-of select=\"translate(child::*[1]/@mml:stretchy, $StrUCAlphabet, $StrLCAlphabet)\"/>\n                </xsl:otherwise>\n            </xsl:choose>\n        </xsl:param>\n        <m:naryPr>\n            <m:chr>\n                <xsl:attribute name=\"m:val\">\n                    <xsl:value-of select=\"$chr\"/>\n                </xsl:attribute>\n            </m:chr>\n            <m:limLoc>\n                <xsl:attribute name=\"m:val\">\n                    <xsl:choose>\n                        <xsl:when test=\"$sMathmlType='munder' or\n\t\t\t\t\t\t\t\t\t$sMathmlType='mover' or\n\t\t\t\t\t\t\t\t\t$sMathmlType='munderover'\">\n                            <xsl:text>undOvr</xsl:text>\n                        </xsl:when>\n                        <xsl:when test=\"$sMathmlType='msub' or\n\t\t\t\t\t                $sMathmlType='msup' or\n\t\t\t\t\t                $sMathmlType='msubsup'\">\n                            <xsl:text>subSup</xsl:text>\n                        </xsl:when>\n                    </xsl:choose>\n                </xsl:attribute>\n            </m:limLoc>\n            <m:grow>\n                <xsl:attribute name=\"m:val\">\n                    <xsl:choose>\n                        <xsl:when test=\"$sGrow='true'\">1</xsl:when>\n                        <xsl:when test=\"$sGrow='false'\">0</xsl:when>\n                        <xsl:when test=\"$chr='&#x222B;'\n                            or $chr='&#x222E;'\n                            or $chr='&#x222F;'\n                            or $chr='&#x2232;'\n                            or $chr='&#x2233;'\n                            or $chr='&#x2229;'\n                            or $chr='&#x222A;'\n                            or $chr='&#x220F;'\n                            or $chr='&#x2211;'\n                            or $chr='&#x22C0;'\n                            or $chr='&#x22C1;'\n                            or $chr='&#x22C2;'\n                            or $chr='&#x22C3;'\">1</xsl:when>\n                        <xsl:otherwise>0</xsl:otherwise>\n                    </xsl:choose>\n                </xsl:attribute>\n            </m:grow>\n            <m:subHide>\n                <xsl:attribute name=\"m:val\">\n                    <xsl:choose>\n                        <xsl:when test=\"$sMathmlType='mover' or\n\t\t\t\t\t\t                $sMathmlType='msup'\">\n                            <xsl:text>on</xsl:text>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:text>off</xsl:text>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:attribute>\n            </m:subHide>\n            <m:supHide>\n                <xsl:attribute name=\"m:val\">\n                    <xsl:choose>\n                        <xsl:when test=\"$sMathmlType='munder' or\n\t\t\t\t\t\t                $sMathmlType='msub'\">\n                            <xsl:text>on</xsl:text>\n                        </xsl:when>\n                        <xsl:otherwise>\n                            <xsl:text>off</xsl:text>\n                        </xsl:otherwise>\n                    </xsl:choose>\n                </xsl:attribute>\n            </m:supHide>\n        </m:naryPr>\n    </xsl:template>\n</xsl:stylesheet>"
  },
  {
    "path": "src/main/resources/math-character-aliases.txt",
    "content": "to->rightarrow\ngets->leftarrow\nimplies->Longrightarrow\n"
  },
  {
    "path": "src/main/resources/math-character-circled.txt",
    "content": "0->⓪\n1->①\n2->②\n3->③\n4->④\n5->⑤\n6->⑥\n7->⑦\n8->⑧\n9->⑨\n10->⑩\n11->⑪\n12->⑫\n13->⑬\n14->⑭\n15->⑮\n16->⑯\n17->⑰\n18->⑱\n19->⑲\n20->⑳\n21->㉑\n22->㉒\n23->㉓\n24->㉔\n25->㉕\n26->㉖\n27->㉗\n28->㉘\n29->㉙\n30->㉚\n31->㉛\n32->㉜\n33->㉝\n34->㉞\n35->㉟\n36->㊱\n37->㊲\n38->㊳\n39->㊴\n40->㊵\n41->㊶\n42->㊷\n43->㊸\n44->㊹\n45->㊺\n46->㊻\n47->㊼\n48->㊽\n49->㊾\n50->㊿\nA->Ⓐ\nB->Ⓑ\nC->Ⓒ\nD->Ⓓ\nE->Ⓔ\nF->Ⓕ\nG->Ⓖ\nH->Ⓗ\nI->Ⓘ\nJ->Ⓙ\nK->Ⓚ\nL->Ⓛ\nM->Ⓜ\nN->Ⓝ\nO->Ⓞ\nP->Ⓟ\nQ->Ⓠ\nR->Ⓡ\nS->Ⓢ\nT->Ⓣ\nU->Ⓤ\nV->Ⓥ\nW->Ⓦ\nX->Ⓧ\nY->Ⓨ\nZ->Ⓩ\na->ⓐ\nb->ⓑ\nc->ⓒ\nd->ⓓ\ne->ⓔ\nf->ⓕ\ng->ⓖ\nh->ⓗ\ni->ⓘ\nj->ⓙ\nk->ⓚ\nl->ⓛ\nm->ⓜ\nn->ⓝ\no->ⓞ\np->ⓟ\nq->ⓠ\nr->ⓡ\ns->ⓢ\nt->ⓣ\nu->ⓤ\nv->ⓥ\nw->ⓦ\nx->ⓧ\ny->ⓨ\nz->ⓩ\n一->㊀\n二->㊁\n三->㊂\n四->㊃\n五->㊄\n六->㊅\n七->㊆\n八->㊇\n九->㊈\n十->㊉"
  },
  {
    "path": "src/test/java/org/ddr/poi/FileReader.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi;\n\nimport org.apache.commons.io.IOUtils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * @author Draco\n * @since 2021-04-12 21:20\n */\npublic class FileReader {\n    public static String readFile(String resourceFile) throws IOException {\n        try (InputStream inputStream = FileReader.class.getResourceAsStream(resourceFile)) {\n            return IOUtils.toString(inputStream, StandardCharsets.UTF_8);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/ddr/poi/html/HtmlRenderPolicyTest.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.html;\n\nimport com.deepoove.poi.XWPFTemplate;\nimport com.deepoove.poi.config.Configure;\nimport org.ddr.poi.FileReader;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.HashMap;\nimport java.util.Map;\n\nclass HtmlRenderPolicyTest {\n\n    @Test\n    void doRender() throws IOException {\n        HtmlRenderPolicy htmlRenderPolicy = new HtmlRenderPolicy();\n        Configure configure = Configure.builder()\n                .bind(\"teachContent\", htmlRenderPolicy)\n                .bind(\"plainContent\", htmlRenderPolicy)\n                .build();\n        Map<String, Object> data = new HashMap<>(2);\n        data.put(\"teachContent\", FileReader.readFile(\"/1.html\"));\n        data.put(\"plainContent\", FileReader.readFile(\"/2.html\"));\n\n        try (InputStream inputStream = HtmlRenderPolicyTest.class.getResourceAsStream(\"/notes.docx\")) {\n            XWPFTemplate.compile(inputStream, configure).render(data).writeToFile(\"notes_out.docx\");\n        }\n        // 段落内嵌入html测试\n        try (InputStream inputStream = HtmlRenderPolicyTest.class.getResourceAsStream(\"/poi.docx\")) {\n            XWPFTemplate.compile(inputStream, configure).render(data).writeToFile(\"poi_out.docx\");\n        }\n    }\n\n}"
  },
  {
    "path": "src/test/java/org/ddr/poi/latex/LaTeXRenderPolicyTest.java",
    "content": "package org.ddr.poi.latex;\n\nimport com.deepoove.poi.XWPFTemplate;\nimport com.deepoove.poi.config.Configure;\nimport org.ddr.poi.math.EmptyEOfNaryDisplayMode;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.HashMap;\nimport java.util.Map;\n\nclass LaTeXRenderPolicyTest {\n\n    @Test\n    void doRender() throws IOException {\n        LaTeXRenderPolicy laTeXRenderPolicy = new LaTeXRenderPolicy();\n        laTeXRenderPolicy.getConfig().setEmptyEOfNaryOption(EmptyEOfNaryDisplayMode.ZERO_WIDTH_HIDDEN);\n        Configure configure = Configure.builder()\n                .bind(\"math1\", laTeXRenderPolicy)\n                .bind(\"math2\", laTeXRenderPolicy)\n                .bind(\"math3\", laTeXRenderPolicy)\n                .bind(\"math4\", laTeXRenderPolicy)\n                .build();\n        Map<String, Object> data = new HashMap<>(4);\n        // https://www2.ph.ed.ac.uk/snuggletex/documentation/math-mode.html\n        data.put(\"math1\", \"$$ x+2=3 $$\");\n        data.put(\"math2\", \"\\\\[ \\\\sum_{i=1}^{\\\\infty} \\\\frac{1}{n^s} \\n\" +\n                \"= \\\\prod_p \\\\frac{1}{1 - p^{-s}} \\\\tag{1.1}\\\\]\");\n        data.put(\"math3\", \"Product $\\\\prod_{i=a}^{b} f(i) \\\\tag{\\\\textcircled{1}}$ inside text. $\\\\mathbb{N} \\\\mathbf{N} \\\\mathbb{Z} \\\\mathbf{Z} \\\\mathbb{D} \\\\mathbf{D} \\\\mathbb{Q} \\\\mathbf{Q} \\\\mathbb{R} \\\\mathbf{R} \\\\mathbb{C} \\\\mathbf{C}$\");\n        data.put(\"math4\", \"$\\\\lim_{x\\\\to\\\\infty} f(x)$\");\n        try (InputStream inputStream = LaTeXRenderPolicyTest.class.getResourceAsStream(\"/math.docx\")) {\n            XWPFTemplate.compile(inputStream, configure).render(data).writeToFile(\"latex_out.docx\");\n        }\n    }\n}"
  },
  {
    "path": "src/test/java/org/ddr/poi/math/MathMLRenderPolicyTest.java",
    "content": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.ddr.poi.math;\n\nimport com.deepoove.poi.XWPFTemplate;\nimport com.deepoove.poi.config.Configure;\nimport org.ddr.poi.FileReader;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @author Draco\n * @since 2021-04-11 8:33\n */\nclass MathMLRenderPolicyTest {\n\n    @Test\n    void doRender() throws IOException {\n        MathMLRenderPolicy mathMLRenderPolicy = new MathMLRenderPolicy();\n        Configure configure = Configure.builder()\n                .bind(\"math1\", mathMLRenderPolicy)\n                .bind(\"math2\", mathMLRenderPolicy)\n                .bind(\"math3\", mathMLRenderPolicy)\n                .bind(\"math4\", mathMLRenderPolicy)\n                .build();\n        List<String> mathList = new ArrayList<String>(4);\n        for (int i = 0; i < 4; i++) {\n            mathList.add(FileReader.readFile(\"/\" + i + \".xml\"));\n        }\n        Collections.shuffle(mathList);\n        Map<String, Object> data = new HashMap<>(mathList.size());\n\n        for (int i = 0; i < mathList.size(); i++) {\n            data.put(\"math\" + (i + 1), mathList.get(i));\n        }\n        try (InputStream inputStream = MathMLRenderPolicyTest.class.getResourceAsStream(\"/math.docx\")) {\n            XWPFTemplate.compile(inputStream, configure).render(data).writeToFile(\"math_out.docx\");\n        }\n    }\n}"
  },
  {
    "path": "src/test/resources/0.xml",
    "content": "<math>\n    <mtable columnalign=\"right center left\">\n        <mtr>\n            <mtd>\n                <msup>\n                    <mrow>\n                        <mo>(</mo>\n                        <mi>a</mi>\n                        <mo>+</mo>\n                        <mi>b</mi>\n                        <mo>)</mo>\n                    </mrow>\n                    <mn>2</mn>\n                </msup>\n            </mtd>\n            <mtd>\n                <mo>=</mo>\n            </mtd>\n            <mtd>\n                <msup>\n                    <mi>c</mi>\n                    <mn>2</mn>\n                </msup>\n                <mo>+</mo>\n                <mn>4</mn>\n                <mo>⋅</mo>\n                <mo>(</mo>\n                <mfrac>\n                    <mn>1</mn>\n                    <mn>2</mn>\n                </mfrac>\n                <mi>a</mi>\n                <mi>b</mi>\n                <mo>)</mo>\n            </mtd>\n        </mtr>\n        <mtr>\n            <mtd>\n                <msup>\n                    <mi>a</mi>\n                    <mn>2</mn>\n                </msup>\n                <mo>+</mo>\n                <mn>2</mn>\n                <mi>a</mi>\n                <mi>b</mi>\n                <mo>+</mo>\n                <msup>\n                    <mi>b</mi>\n                    <mn>2</mn>\n                </msup>\n            </mtd>\n            <mtd>\n                <mo>=</mo>\n            </mtd>\n            <mtd>\n                <msup>\n                    <mi>c</mi>\n                    <mn>2</mn>\n                </msup>\n                <mo>+</mo>\n                <mn>2</mn>\n                <mi>a</mi>\n                <mi>b</mi>\n            </mtd>\n        </mtr>\n        <mtr>\n            <mtd></mtd>\n        </mtr>\n        <mtr>\n            <mtd>\n                <msup>\n                    <mi>a</mi>\n                    <mn>2</mn>\n                </msup>\n                <mo>+</mo>\n                <msup>\n                    <mi>b</mi>\n                    <mn>2</mn>\n                </msup>\n            </mtd>\n            <mtd>\n                <mo>=</mo>\n            </mtd>\n            <mtd>\n                <msup>\n                    <mi>c</mi>\n                    <mn>2</mn>\n                </msup>\n            </mtd>\n        </mtr>\n    </mtable>\n</math>"
  },
  {
    "path": "src/test/resources/1.html",
    "content": "<p style=\"font: italic small-caps bold 16px/2 cursive, sans-serif;\">\n    <span style=\"font-size: 36px\">你好<strong>世界<i> 你<a\n            href=\"http://www.baidu.com\">敢信</a></i></strong>啊！</span>\n</p>\n<p>\n    <span style=\"font-size: 24px\">你好<a\n            href=\"http://www.baidu.com\"><b>世界<i> 你敢信</i></b>啊！<br/><img\n            src=\"https://profile.csdnimg.cn/1/2/6/1_myhope88\" width=\"32\"/></a></span><img\n        src=\"https://g.csdnimg.cn/static/user-reg-year/2x/15.png\" style=\"width:50%\"/><img\n        src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAQAAABIkb+zAAABk0lEQVR4Ae3asVHcUBSF4V8jJcoghRhyVpSgFiA1dgAlQAlQApSgVQfQATTAxruZV8qUPEbXDXg8b7zPko7n/qeCL7wzF8/zPM9Lmv15/xHAAQ5wgAMcUNGyxw7YTxoumKkffGEJFvjGDFV8YYkWuGDyWizh1kzePimgY/Is8XJ1QOEABzjgH+aAV23ASKUNaEAZEDjTBjyDMmDgRBvwCMqAjiNtwD0oA7aU2oBbUAZ8UmgDrkAZ8E6mDahBGfAKyoCRShvQgDIgcKYNeAZlwMCJNuARlAEdR8RULxVwT0wZH8sEbCmJ6RpbJuCWmAo2ywR8UhDTHbZMwBUxleyWCXgnI6YHbJmAmpiO6VXugd/3hCkDThm0AS+YMuCcoA1YY8qAS0ZtwBumDKgxZUDGhzbgGlMGFGy0AXeYMqBkpw14wJQBx/TagCdMGXDKoA14wZQB5wRtwBpTBlwyagPe/OlvWoADcu33+57Ja5ICWiZvRcASLbBihr4nIgRumKmKlh47YB0tK2Yup/jL5cyb53me9wv95P/HjXVvlAAAAABJRU5ErkJggg==\"/>\n</p>"
  },
  {
    "path": "src/test/resources/1.xml",
    "content": "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">\n    <mrow>\n        <mrow>\n            <msup>\n                <mi>x</mi>\n                <mn>2</mn>\n            </msup>\n            <mo>+</mo>\n            <mrow>\n                <mn>4</mn>\n                <mo>&InvisibleTimes;</mo>\n                <mi>x</mi>\n            </mrow>\n            <mo>+</mo>\n            <mn>4</mn>\n        </mrow>\n        <mo>=</mo>\n        <mn>0</mn>\n    </mrow>\n</math>"
  },
  {
    "path": "src/test/resources/2.html",
    "content": "<p><span style=\"font-size: 36px\">你好<strong>世界<i> 你敢信</i></strong>啊！</span></p><p><span\n        style=\"font-size: 24px\">你好<b>世界<i> 你敢信</i></b>啊！</span><br/><img\n        src=\"https://profile.csdnimg.cn/1/2/6/1_myhope88\"/><img\n        src=\"https://g.csdnimg.cn/static/user-reg-year/2x/15.png\"/><img\n        src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAQAAABIkb+zAAABk0lEQVR4Ae3asVHcUBSF4V8jJcoghRhyVpSgFiA1dgAlQAlQApSgVQfQATTAxruZV8qUPEbXDXg8b7zPko7n/qeCL7wzF8/zPM9Lmv15/xHAAQ5wgAMcUNGyxw7YTxoumKkffGEJFvjGDFV8YYkWuGDyWizh1kzePimgY/Is8XJ1QOEABzjgH+aAV23ASKUNaEAZEDjTBjyDMmDgRBvwCMqAjiNtwD0oA7aU2oBbUAZ8UmgDrkAZ8E6mDahBGfAKyoCRShvQgDIgcKYNeAZlwMCJNuARlAEdR8RULxVwT0wZH8sEbCmJ6RpbJuCWmAo2ywR8UhDTHbZMwBUxleyWCXgnI6YHbJmAmpiO6VXugd/3hCkDThm0AS+YMuCcoA1YY8qAS0ZtwBumDKgxZUDGhzbgGlMGFGy0AXeYMqBkpw14wJQBx/TagCdMGXDKoA14wZQB5wRtwBpTBlwyagPe/OlvWoADcu33+57Ja5ICWiZvRcASLbBihr4nIgRumKmKlh47YB0tK2Yup/jL5cyb53me9wv95P/HjXVvlAAAAABJRU5ErkJggg==\"/>\n</p>\n<p>\n    <math xmlns=\"http://www.w3.org/1998/Math/MathML\">\n        <msqrt>\n            <mn>4</mn>\n        </msqrt>\n    </math>&nbsp;<math xmlns=\"http://www.w3.org/1998/Math/MathML\">\n    <mfrac bevelled=\"true\">\n        <mn>1</mn>\n        <mn>4</mn>\n    </mfrac>\n</math>\n</p>\n\n<p><u><em><strong><span id=\"cke_bm_1103S\" style=\"display:none\">&nbsp;</span>&nbsp;&nbsp;<u>王<span\n        style=\"text-decoration: none;\">府<s>井</s></span><s>围殴</s>皮肤</u>较为</strong></em></u></p>\n\n<h1><big><u><em><strong>威风威风文&nbsp; &nbsp;<big><u><em><strong><big><u><em><strong><big><u><em><strong><img alt=\"cool\"\n                                                                                                           bizcode=\"\"\n                                                                                                           fileid=\"\"\n                                                                                                           height=\"23\"\n                                                                                                           src=\"https://editor_baktest.bakclass.com/plugins/smiley/images/shades_smile.png\"\n                                                                                                           title=\"cool\"\n                                                                                                           width=\"23\"/></strong></em></u></big></strong></em></u></big></strong></em></u></big><a\n        href=\"http://www.baidu.com\">http://www.baidu.com</a></strong></em></u></big></h1>\n\n<ol>\n    <li style=\"color: red\"><u><em><strong>违法未</strong></em></u></li>\n    <li><u><em><strong>违法未</strong></em></u></li>\n</ol>\n\n<p style=\"color: rgb(0,255,0)\">今天（21日）0时，位于新疆南部的<span style=\"color: hsla(30, 100%, 50%, .3);\">塔里木油田</span>年油气产量达到3003.12万吨，成为我国油气上产重要战略接替区。\n</p>\n\n<p style=\"color: hsl(30, 100%, 50%);\">\n    　　位于大北博孜气区的大北902井近日喷出高产气流，今年这里试油的6口井均获40万立方米以上高产。目前，天山南麓还有60多部钻机正在钻进，近两年，塔里木油田超过100口气井获高产，新建天然气产能90亿立方米。</p>\n\n<p>　　<img alt=\"\" bizcode=\"\" fileid=\"\"\n          src=\"https://dss1.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/weather/icons/a0.png\"/><br/>\n    &nbsp;</p>\n\n<p style=\"color: rgba(0,255,0,0.6)\">　　中石油塔里木油田公司总经理 杨学文：&ldquo;十三五&rdquo;期间，塔里木油田大力提升勘探开发力度，已经落实两个万亿方大气区，形成10亿吨级大油气区，为新一轮的油气增储上产奠定了坚实的资源基础。</p>\n\n<p>\n    　　56万平方公里的塔里木盆地是我国陆上最大的含油气盆地，盆地遍布大漠戈壁，油气大多蕴藏在超过6000米的地宫深处，面临超深、超高温、超高压等极限考验，是世界级勘探禁区。油田的科技工作者攻克了一系列世界级难题，探明油气储量26.7亿吨。</p>\n\n<hr/>\n<hr/>\n<hr/>\n<ul style=\"list-style: square; margin-left: 4em\">\n    <li><u><em><strong>威风威风</strong></em></u></li>\n    <li><u><em><strong>违法未</strong></em></u>\n        <ol style=\"list-style-type: lower-roman; margin-right:4em\">\n            <li><p style=\"line-height: 4\">测试嵌套123213和纳税空间圣诞节ask的风景啊的肌肤啊撒旦解放深刻搭街坊埃德加发生的纠纷啊深刻搭街坊啊撒旦看风景啊的肌肤阿斯达克警方啊撒旦解放啊可是大家发深刻搭街坊阿斯达克激发深刻搭街坊ask的风景阿斯达克放假啊上的飞机啊扣税的，上的飞机ask大家罚款。</p></li>\n            <li>\n                <b>测试嵌套2222</b>\n                <ol type=\"A\" style=\"margin-right: 2em\">\n                    <li>测试嵌套3333</li>\n                    <li>测试嵌套123213和纳税空间圣诞节ask的风景啊的肌肤啊撒旦解放深刻搭街坊埃德加发生的纠纷啊深刻搭街坊啊撒旦看风景啊的肌肤阿斯达克警方啊撒旦解放啊可是大家发深刻搭街坊阿斯达克激发深刻搭街坊ask的风景阿斯达克放假啊上的飞机啊扣税的</li>\n                </ol>\n            </li>\n        </ol>\n    </li>\n    <li><u><em><strong>违法未</strong></em></u>\n        <ol type=\"A\">\n            <li>测试另一个嵌套123213</li>\n            <li>测试另一个嵌套2222</li>\n        </ol>\n    </li>\n</ul>\n\n<p>&nbsp;</p>\n\n<p><img alt=\"\" bizcode=\"\" fileid=\"\" height=\"431.8407960199005\"\n        src=\"https://test-max-public.oss-cn-hangzhou.aliyuncs.com/max/site/2020-12-29/Bj21JqjsT6WgIu3BO4egbA.png\"\n        width=\"800\"/></p>\n\n<img src=\"https://cdn.jsdelivr.net/gh/draco1023/poi-tl-ext@feature-svg/src/test/resources/error-403.svg\"/>\n\n<table cellspacing=\"0\" class=\"Table\"\n       style=\"border-collapse:collapse; border:none; font-family:&quot;Times New Roman&quot;; font-size:13px; margin-left:-9px; width:566px\">\n    <tbody>\n    <tr>\n        <td colspan=\"3\"\n            style=\"border-bottom:2px solid black; border-left:1px solid black; border-right:1px solid black; border-top:1px solid black; vertical-align:center; width:484px\">\n            <p align=\"center\" style=\"text-align:center\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:等线\"><strong><span style=\"font-size:10.5000pt\"><span\n                    style=\"font-family:宋体\"><strong>（导）学案</strong></span></span></strong></span></span></p>\n        </td>\n        <td style=\"border-bottom:2px solid black; border-left:none; border-right:1px solid black; border-top:1px solid black; vertical-align:center; width:81px\">\n            <p align=\"center\" style=\"text-align:center\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:等线\"><strong><span style=\"font-size:10.5000pt\"><span\n                    style=\"font-family:宋体\"><strong>备 注</strong></span></span></strong></span></span></p>\n        </td>\n    </tr>\n    <tr>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:center; width:85px\">\n            <p style=\"text-align:left\"><span style=\"font-size:10.5pt\"><span style=\"font-family:等线\"><strong><span\n                    style=\"font-size:10.5000pt\"><span\n                    style=\"font-family:宋体\"><strong>学习目标</strong></span></span></strong></span></span></p>\n        </td>\n        <td colspan=\"2\"\n            style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:399px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:2px solid black; vertical-align:center; width:81px\">\n            <table>\n                <thead>\n                <tr>\n                    <th colspan=\"2\">The table header</th>\n                </tr>\n                </thead>\n                <tbody>\n                <tr>\n                    <td>The table body</td>\n                    <td>with two columns</td>\n                </tr>\n                </tbody>\n            </table>\n            <table>\n                <thead>\n                <tr>\n                    <th colspan=\"2\">The table header</th>\n                </tr>\n                </thead>\n                <tbody>\n                <tr>\n                    <td>The table body</td>\n                    <td>with two columns</td>\n                </tr>\n                </tbody>\n            </table>\n            ttttttt\n            <p style=\"text-align:left\">test&nbsp;</p>\n        </td>\n    </tr>\n    <tr>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:center; width:85px\">\n            <p style=\"text-align:left\"><span style=\"font-size:10.5pt\"><span style=\"font-family:等线\"><strong><span\n                    style=\"font-size:10.5000pt\"><span\n                    style=\"font-family:宋体\"><strong>学习重点</strong></span></span></strong></span></span></p>\n        </td>\n        <td colspan=\"2\"\n            style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:399px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:81px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n    </tr>\n    <tr>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:center; width:85px\">\n            <p style=\"text-align:left\"><span style=\"font-size:10.5pt\"><span style=\"font-family:等线\"><strong><span\n                    style=\"font-size:10.5000pt\"><span\n                    style=\"font-family:宋体\"><strong>知识链接</strong></span></span></strong></span></span></p>\n        </td>\n        <td colspan=\"2\"\n            style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:399px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:81px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n    </tr>\n    <tr>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:center; width:85px\">\n            <p style=\"text-align:left\"><span style=\"font-size:10.5pt\"><span style=\"font-family:等线\"><strong><span\n                    style=\"font-size:10.5000pt\"><span\n                    style=\"font-family:宋体\"><strong>学法指导</strong></span></span></strong></span></span></p>\n        </td>\n        <td colspan=\"2\"\n            style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:399px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:81px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n    </tr>\n    <tr>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:2px solid black; vertical-align:center; width:85px\">\n            <p style=\"text-align:left\"><span style=\"font-size:10.5pt\"><span style=\"font-family:等线\"><strong><span\n                    style=\"font-size:10.5000pt\"><span\n                    style=\"font-family:宋体\"><strong>学习</strong></span></span></strong><strong><span\n                    style=\"font-size:10.5000pt\"><span style=\"font-family:宋体\"><strong>过程</strong></span></span></strong></span></span>\n            </p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:2px solid black; vertical-align:center; width:207px\">\n            <p style=\"text-align:left\"><span style=\"font-size:10.5pt\"><span style=\"font-family:等线\"><span\n                    style=\"font-size:10.5000pt\"><span style=\"font-family:宋体\">问题与任务</span></span></span></span></p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:2px solid black; vertical-align:center; width:191px\">\n            <p style=\"text-align:left\"><span style=\"font-size:10.5pt\"><span style=\"font-family:等线\"><span\n                    style=\"font-size:10.5000pt\"><span style=\"font-family:宋体\">方法与要求</span></span></span></span></p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:2px solid black; vertical-align:center; width:81px\">\n            <p style=\"text-align:left\"><span style=\"font-size:10.5pt\"><span style=\"font-family:等线\"><span\n                    style=\"font-size:10.5000pt\"><span style=\"font-family:宋体\">-</span></span></span></span></p>\n        </td>\n    </tr>\n    <tr>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:center; width:85px\">\n            <p style=\"margin-right:1px; text-align:left\">&nbsp;</p>\n\n            <p style=\"margin-right:1px; text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:207px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:191px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:top; width:81px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n    </tr>\n    <tr>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:center; width:85px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:207px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:191px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:top; width:81px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n    </tr>\n    <tr>\n        <td style=\"border-bottom:2px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:center; width:85px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:2px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:207px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:2px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:191px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:top; width:81px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n    </tr>\n    <tr>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:center; width:85px\">\n            <p style=\"text-align:left\"><span style=\"font-size:10.5pt\"><span style=\"font-family:等线\"><strong><span\n                    style=\"font-size:10.5000pt\"><span\n                    style=\"font-family:宋体\"><strong>达标检测</strong></span></span></strong></span></span></p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:207px\">\n            <p style=\"text-align:left\"><span style=\"font-size:10.5pt\"><span style=\"font-family:等线\"><span\n                    style=\"font-size:10.5000pt\"><span style=\"font-family:宋体\">课内</span></span><span\n                    style=\"font-size:10.5000pt\"><span style=\"font-family:宋体\">作业</span></span></span></span></p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:191px\">\n            <p style=\"text-align:left\"><span style=\"font-size:10.5pt\"><span style=\"font-family:等线\"><span\n                    style=\"font-size:10.5000pt\"><span style=\"font-family:宋体\">课外作业</span></span></span></span></p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:2px solid black; vertical-align:center; width:81px\">\n            <p style=\"text-align:left\"><span style=\"font-size:10.5pt\"><span style=\"font-family:等线\"><span\n                    style=\"font-size:10.5000pt\"><span style=\"font-family:宋体\">-</span></span></span></span></p>\n        </td>\n    </tr>\n    <tr>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:center; width:85px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:207px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:191px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:81px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n    </tr>\n    <tr>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:center; width:85px\">\n            <p style=\"text-align:left\"><span style=\"font-size:10.5pt\"><span style=\"font-family:等线\"><strong><span\n                    style=\"font-size:10.5000pt\"><span\n                    style=\"font-family:宋体\"><strong>总结提升</strong></span></span></strong></span></span></p>\n        </td>\n        <td colspan=\"2\"\n            style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:399px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:81px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n    </tr>\n    <tr>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:center; width:85px\">\n            <p style=\"text-align:left\"><span style=\"font-size:10.5pt\"><span style=\"font-family:等线\"><strong><span\n                    style=\"font-size:10.5000pt\"><span\n                    style=\"font-family:宋体\"><strong>课后反思</strong></span></span></strong></span></span></p>\n        </td>\n        <td colspan=\"2\"\n            style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:399px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:none; border-right:1px solid black; border-top:none; vertical-align:center; width:81px\">\n            <p style=\"text-align:left\">&nbsp;</p>\n        </td>\n    </tr>\n    </tbody>\n</table>\n<p>test</p>\n<p>创建和编辑页面是不能被 keep-alive 缓存的，因为keep-alive 的 include 目前不支持根据路由来缓存，所以目前都是基于 component name 来进行缓存的。如果你想类似的实现缓存效果，可以使用\n    localStorage 等浏览器缓存方案。或者不要使用 keep-alive 的 include，直接缓存所有页面。详情见&nbsp;<a\n            href=\"https://panjiachen.github.io/vue-element-admin-site/guide/essentials/tags-view.html\" target=\"_blank\"\n            rel=\"noopener\">Document</a></p>\n<table style=\"border-collapse: collapse; width: 50%; float: right\" border=\"1\">\n    <tbody>\n    <tr>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n    </tr>\n    <tr>\n        <td style=\"width: 12.5%;\">士大夫</td>\n        <td style=\"width: 12.5%;\">发</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n    </tr>\n    <tr>\n        <td style=\"width: 12.5%;\">&nbsp; 啊发撒地方</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n    </tr>\n    <tr>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">撒地方的撒</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n    </tr>\n    <tr>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n    </tr>\n    <tr>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n    </tr>\n    <tr>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n        <td style=\"width: 12.5%;\">&nbsp;</td>\n    </tr>\n    </tbody>\n</table>\n<h1 class=\"ui  header\">java生成word的几种方案</h1>\n<table cellspacing=\"0\" class=\"Table\"\n       style=\"border-collapse:collapse; border:none; font-family:&quot;Times New Roman&quot;; font-size:13px\">\n    <caption><p><b>方案描述表</b></p>\n        <p><i style=\"font-size: smaller\">方案描述表副标题</i></p></caption>\n    <colgroup>\n        <col/>\n        <col/>\n        <col span=\"2\" style=\"background-color: red\"/>\n        <col span=\"2\" style=\"font-weight: bolder; background-color: yellow\"/>\n    </colgroup>\n    <tbody>\n    <tr>\n        <td rowspan=\"2\"\n            style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:1px solid black; vertical-align:top; width:92px\">\n            <p style=\"text-align:justify\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:&quot;Times New Roman&quot;\"><span style=\"font-size:12.0000pt\"><span\n                    style=\"font-family:'Times New Roman'\"><span\n                    style=\"font-family:宋体\">序号</span></span></span></span></span></p>\n        </td>\n        <td rowspan=\"2\"\n            style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:1px solid black; vertical-align:top; width:92px\">\n            <p style=\"text-align:justify\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:&quot;Times New Roman&quot;\"><span style=\"font-size:12.0000pt\"><span\n                    style=\"font-family:'Times New Roman'\"><span style=\"font-family:宋体\">荷载</span><span\n                    style=\"font-family:Times New Roman\">(kN)</span></span></span></span></span></p>\n        </td>\n        <td colspan=\"2\"\n            style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:1px solid black; vertical-align:top; width:184px\">\n            <p style=\"text-align:justify\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:&quot;Times New Roman&quot;\"><span style=\"font-size:12.0000pt\"><span\n                    style=\"font-family:'Times New Roman'\"><span style=\"font-family:宋体\">历时</span><span\n                    style=\"font-family:Times New Roman\">(min)</span></span></span></span></span></p>\n        </td>\n        <td colspan=\"2\"\n            style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:1px solid black; vertical-align:top; width:184px\">\n            <p style=\"text-align:justify\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:&quot;Times New Roman&quot;\"><span style=\"font-size:12.0000pt\"><span\n                    style=\"font-family:'Times New Roman'\"><span style=\"font-family:宋体\">沉降</span><span\n                    style=\"font-family:Times New Roman\">(mm)</span></span></span></span></span></p>\n        </td>\n    </tr>\n    <tr>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:top; width:92px\">\n            <p style=\"text-align:justify\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:&quot;Times New Roman&quot;\"><span style=\"font-size:12.0000pt\"><span\n                    style=\"font-family:'Times New Roman'\"><span\n                    style=\"font-family:宋体\">本级1</span></span></span></span></span></p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:top; width:92px\">\n            <p style=\"text-align:justify\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:&quot;Times New Roman&quot;\"><span style=\"font-size:12.0000pt\"><span\n                    style=\"font-family:'Times New Roman'\"><span\n                    style=\"font-family:宋体\">累计1</span></span></span></span></span></p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:top; width:92px\">\n            <p style=\"text-align:justify\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:&quot;Times New Roman&quot;\"><span style=\"font-size:12.0000pt\"><span\n                    style=\"font-family:'Times New Roman'\"><span\n                    style=\"font-family:宋体\">本级2</span></span></span></span></span></p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:top; width:92px\">\n            <p style=\"text-align:justify\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:&quot;Times New Roman&quot;\"><span style=\"font-size:12.0000pt\"><span\n                    style=\"font-family:'Times New Roman'\"><span\n                    style=\"font-family:宋体\">累计2</span></span></span></span></span></p>\n        </td>\n\n    </tr>\n    <tr>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:top; width:92px\">\n            <p style=\"text-align:justify\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:&quot;Times New Roman&quot;\"><span style=\"font-size:12.0000pt\"><span\n                    style=\"font-family:'Times New Roman'\">111</span></span></span></span></p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:top; width:92px\">\n            <p style=\"text-align:justify\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:&quot;Times New Roman&quot;\"><span style=\"font-size:12.0000pt\"><span\n                    style=\"font-family:'Times New Roman'\">222</span></span></span></span></p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:top; width:92px\">\n            <p style=\"text-align:justify\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:&quot;Times New Roman&quot;\"><span style=\"font-size:12.0000pt\"><span\n                    style=\"font-family:'Times New Roman'\">333</span></span></span></span></p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:top; width:92px\">\n            <p style=\"text-align:justify\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:&quot;Times New Roman&quot;\"><span style=\"font-size:12.0000pt\"><span\n                    style=\"font-family:'Times New Roman'\">444</span></span></span></span></p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:top; width:92px\">\n            <p style=\"text-align:justify\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:&quot;Times New Roman&quot;\"><span style=\"font-size:12.0000pt\"><span\n                    style=\"font-family:'Times New Roman'\">555</span></span></span></span></p>\n        </td>\n        <td style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:top; width:92px\">\n            <p style=\"text-align:justify\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:&quot;Times New Roman&quot;\"><span style=\"font-size:12.0000pt\"><span\n                    style=\"font-family:'Times New Roman'\">666</span></span></span></span></p>\n        </td>\n\n    </tr>\n\n\n    <tr>\n        <td colspan=\"6\"\n            style=\"border-bottom:1px solid black; border-left:1px solid black; border-right:1px solid black; border-top:none; vertical-align:top; width:553px\">\n            <p style=\"text-align:justify\"><span style=\"font-size:10.5pt\"><span\n                    style=\"font-family:&quot;Times New Roman&quot;\"><span style=\"font-size:12.0000pt\"><span\n                    style=\"font-family:'Times New Roman'\"><span style=\"font-family:宋体\">最大沉降量：</span><span\n                    style=\"font-family:Times New Roman\">19.88mm &nbsp;&nbsp;&nbsp;</span><span style=\"font-family:宋体\">最大回弹量：</span><span\n                    style=\"font-family:Times New Roman\">8.66mm </span><span style=\"font-family:宋体\">回弹率：</span><span\n                    style=\"font-family:Times New Roman\">43.56%</span></span></span></span></span></p>\n        </td>\n\n    </tr>\n    </tbody>\n</table>\n\n<table width=\"100%\" border=\"1\">\n    <tr>\n        <td colspan=\"2\" style=\"text-align:center;width:20%\">\n            <p>111111111</p>\n        </td>\n        <td colspan=\"2\" style=\"text-align:center;width:80%\">\n            11111\n        </td>\n    </tr>\n    <tr>\n        <td colspan=\"2\" rowspan=\"2\" style=\"text-align:center;\">\n            1111\n        </td>\n        <td colspan=\"2\" style=\"text-align:left;\">\n            123445\n        </td>\n    </tr>\n    <tr>\n        <td colspan=\"2\">\n            <p class=\"promptFont\">\n                1、啊啊啊啊啊啊啊\n            </p>\n            <p class=\"promptFont\">\n                2、呃呃呃呃呃呃呃\n            </p>\n            <p class=\"promptFont\">\n                3、噢噢噢噢噢噢噢\n            </p>\n        </td>\n    </tr>\n    <tr>\n        <td colspan=\"2\" style=\"text-align:center;\" rowspan=\"3\">\n            111111\n        </td>\n        <td style=\"text-align:left;width:500px;\" class=\"promptFont\">\n            2\n        </td>\n        <td style=\"text-align:left;\" class=\"promptFont\">\n            1\n        </td>\n    </tr>\n    <tr>\n        <td style=\"text-align:left;width:500px;\" class=\"promptFont\">\n            11111\n        </td>\n        <td style=\"text-align:left;\" class=\"promptFont\">\n            疾控科考虑考虑萨芬框架啊沙发上\n        </td>\n    </tr>\n    <tr>\n        <td style=\"text-align:left;width:500px;\" class=\"promptFont\">\n            售后服务 (10.0分)\n        </td>\n        <td style=\"text-align:left;\" class=\"promptFont\">\n            无售后服务\n        </td>\n    </tr>\n    <tr>\n        <td colspan=\"2\" style=\"text-align:center;\" rowspan=\"2\">\n            333\n        </td>\n        <td style=\"text-align:left;width:500px;\" class=\"promptFont\">\n            1111\n        </td>\n        <td style=\"text-align:left;\" class=\"promptFont\">\n            111\n        </td>\n    </tr>\n    <tr>\n        <td style=\"text-align:left;width:500px;\" class=\"promptFont\">\n            11111\n        </td>\n        <td style=\"text-align:left;\" class=\"promptFont\">\n            啥打发时间开了房卡拉斯科发链接\n        </td>\n    </tr>\n    <tr>\n        <td colspan=\"2\" style=\"text-align:center;\" rowspan=\"1\">\n            333333\n        </td>\n        <td style=\"text-align:left;width:500px;\" class=\"promptFont\">\n            33333333333\n        </td>\n        <td style=\"text-align:left;\" class=\"promptFont\">\n            333333333333333333333\n        </td>\n    </tr>\n</table>\n\n<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\"\n     viewBox=\"0 0 270 451\" style=\"enable-background:new 0 0 270 451;\" xml:space=\"preserve\">\n<style type=\"text/css\">\n\t.st0 {\n        display: none;\n    }\n\n    .st1 {\n        display: inline;\n    }\n\n    .st2 {\n        fill: #FFFFFF;\n        stroke: #FFFFFF;\n        stroke-width: 3;\n        stroke-miterlimit: 10;\n    }\n\n    .st3 {\n        fill: #B6BE00;\n    }\n\n    .st4 {\n        fill: #F0F0F0;\n    }\n\n    .st5 {\n        fill: #6664d1;\n    }\n\n    .st6 {\n        fill: #E96065;\n    }\n\n    .st7 {\n        fill: #FFFFFF;\n    }\n</style>\n    <g>\n\t<g class=\"st0\">\n\t\t<g class=\"st1\">\n\n\t\t\t\t<rect x=\"-394.2\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 -123.7502 -68.9567)\"\n                      class=\"st2\" width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"-352.8\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 -104.721 -34.1118)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"-312\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 -85.9828 0.2002)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"-270.6\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 -66.9536 35.045)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"-228.6\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 -47.6564 70.3806)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"-187.2\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 -28.6273 105.2254)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"-146.4\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 -9.889 139.5374)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"-105\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 9.1401 174.3823)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\t\t</g>\n        <g class=\"st1\">\n\n\t\t\t\t<rect x=\"-69.2\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 25.5577 204.4448)\" class=\"st2\"\n                      width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"-27.8\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 44.5869 239.2897)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"13\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 63.3251 273.6017)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"54.4\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 82.3543 308.4465)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"96.4\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 101.6514 343.7821)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"137.8\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 120.6806 378.6269)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"178.6\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 139.4188 412.9389)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"220\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 158.448 447.7838)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\t\t</g>\n        <g class=\"st1\">\n\n\t\t\t\t<rect x=\"263.2\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 178.2979 484.1314)\" class=\"st2\"\n                      width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"304.6\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 197.3271 518.9763)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"345.4\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 216.0653 553.2883)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"386.8\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 235.0945 588.1331)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"428.8\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 254.3916 623.4687)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"470.2\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 273.4208 658.3135)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"511\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 292.159 692.6255)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\n            <rect x=\"552.4\" y=\"74.3\" transform=\"matrix(0.5405 -0.8413 0.8413 0.5405 311.1882 727.4703)\" class=\"st2\"\n                  width=\"538.4\" height=\"9\"/>\n\t\t</g>\n\t</g>\n</g>\n    <path class=\"st3\" d=\"M191.4,404.2c12.7,14.7,16.7,45.8,16.7,45.8h15.7h5h21.7c1-37,18.5-61.7,18.5-61.7\n\tc-14.9,7.7-21.1,33.9-21.1,33.9c-5.1-29.8,9.8-69.9,9.8-69.9c-14.4,13.4-19.5,52.4-19.5,52.4c-0.6-10.5-2.2-20.2-4.3-28.9\n\tc0.3-1,0.5-1.5,0.5-1.5c-0.2,0.2-0.5,0.5-0.7,0.7c-8.6-34.9-25.8-55.1-25.8-55.1c10.5,16.2,15.6,45.8,17.9,68.8\n\tc-2.1,5.6-3.7,11.5-4.7,16.3c-1.1-1.5-2.2-2.8-3.4-4c-5.5-32.6-21.9-51.8-21.9-51.8c6.9,10.6,10.7,28.6,12.9,44.7\n\tc-3.7-2.2-6.5-3-6.5-3c2.8,3.2,5.3,7.1,7.4,11.2c1.5,14.6,1.7,26,1.7,26C208.1,409.4,191.4,404.2,191.4,404.2z M227.1,403.9\n\tc0.7,10.7,0.8,17.8,0.8,17.8c-0.4-2.5-1.1-4.9-1.8-7C226.2,411.1,226.6,407.5,227.1,403.9z\"/>\n    <g>\n\t<g>\n\t\t<polygon class=\"st4\" points=\"201.6,436 181.7,436 117.7,194.3 137.6,194.3 \t\t\"/>\n        <path class=\"st5\" d=\"M137.6,194.3l64,241.7h-19.9l-64-241.7H137.6 M142.2,188.3h-4.6h-19.9h-7.8l2,7.5l64,241.7l1.2,4.5h4.6h19.9\n\t\t\th7.8l-2-7.5l-64-241.7L142.2,188.3L142.2,188.3z\"/>\n\t</g>\n        <rect x=\"143.9\" y=\"436\" class=\"st5\" width=\"91.5\" height=\"13.9\"/>\n</g>\n    <g>\n\t<g>\n\t\t<g>\n\t\t\t<g>\n\t\t\t\t<path class=\"st4\" d=\"M108.2,207.2c-55,0-99.8-44.8-99.8-99.8c0-55,44.8-99.8,99.8-99.8c55,0,99.8,44.8,99.8,99.8\n\t\t\t\t\tC208,162.4,163.2,207.2,108.2,207.2L108.2,207.2z\"/>\n                <path class=\"st5\" d=\"M108.2,7.6c55,0,99.8,44.8,99.8,99.8c0,55-44.8,99.8-99.8,99.8c-55,0-99.8-44.8-99.8-99.8\n\t\t\t\t\tC8.4,52.4,53.2,7.6,108.2,7.6 M108.2,1.6C49.9,1.6,2.4,49.1,2.4,107.4s47.5,105.8,105.8,105.8c58.3,0,105.8-47.5,105.8-105.8\n\t\t\t\t\tS166.5,1.6,108.2,1.6L108.2,1.6z\"/>\n\t\t\t</g>\n\t\t</g>\n        <path class=\"st6\" d=\"M108.2,18c-49.4,0-89.4,40-89.4,89.4c0,49.4,40,89.4,89.4,89.4c49.4,0,89.4-40,89.4-89.4\n\t\t\tC197.6,58,157.6,18,108.2,18z\"/>\n\t</g>\n        <path class=\"st7\" d=\"M166.3,123.9H51.3c-6.6,0-12-5.4-12-12v-9.7c0-6.6,5.4-12,12-12h115.1c6.6,0,12,5.4,12,12v9.7\n\t\tC178.3,118.5,172.9,123.9,166.3,123.9z\"/>\n</g>\n</svg>\n\n<p>\n    <latex>$$ x+2=3 $$</latex>&emsp;\n    <latex>Product $\\prod_{i=a}^{b} f(i)$ inside text. $\\mathbb{N} \\mathbf{N} \\mathbb{Z} \\mathbf{Z} \\mathbb{D}\n        \\mathbf{D} \\mathbb{Q} \\mathbf{Q} \\mathbb{R} \\mathbf{R} \\mathbb{C} \\mathbf{C}$\n    </latex>\n</p>\n<p>\n    <latex>\\[ \\sum_{i=1}^{\\infty} \\frac{1}{n^s} = \\prod_p \\frac{1}{1 - p^{-s}} \\]</latex>\n</p>"
  },
  {
    "path": "src/test/resources/2.xml",
    "content": "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">\n    <msqrt>\n        <mn>4</mn>\n    </msqrt>\n</math>"
  },
  {
    "path": "src/test/resources/3.xml",
    "content": "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">\n    <mi>&#x03C0;<!-- π --></mi>\n    <mo>&#x2062;<!-- &InvisibleTimes; --></mo>\n    <msup>\n        <mi>r</mi>\n        <mn>2</mn>\n    </msup>\n</math>"
  },
  {
    "path": "src/test/resources/simplelogger.properties",
    "content": "#\n# Copyright 2016 - 2021 Draco, https://github.com/draco1023\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\norg.slf4j.simpleLogger.logFile=System.out\norg.slf4j.simpleLogger.defaultLogLevel=debug\norg.slf4j.simpleLogger.showDateTime=true"
  }
]