一:由于需求需要完成题库系统,用户上传word的试卷,试卷中包含数学、化学、物理公式(OMML),图片以及带有公式图片的复杂表格,将word试卷系统拆分成一个一个的题目,题目中包含(题目类型、题文、选项、答案、解析)并保持基本样式不失真

)

2:解析出来题目的Json格式

[
    {
        "title":"【试卷】学校2020年第二学期期末考试卷",
        "subject":"【科目】数学",
        "number":null,
        "question":"【题文】<table  border=1><tr><td><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><msup><mrow><mi>a</mi></mrow><mrow><mi>n</mi></mrow></msup><msup><mrow><mi>a</mi></mrow><mrow><mi>m</mi></mrow></msup><mo>=</mo><msup><mrow><mi>a</mi></mrow><mrow><mi>n</mi><mo>+</mo><mi>m</mi></mrow></msup></math></td><td>≌∏█♀</td><td><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><msup><mrow><mi>a</mi></mrow><mrow><mn>2</mn></mrow></msup><mo>+</mo><msup><mrow><mi>b</mi></mrow><mrow><mn>2</mn></mrow></msup><mo>=</mo><msup><mrow><mi>c</mi></mrow><mrow><mn>2</mn></mrow></msup></math></td></tr></table>若<?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi mathvariant="normal">x</mi><mo>≥</mo><mi mathvariant="normal">y</mi></math>,<?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi mathvariant="normal">y</mi><mo>-</mo><mn>2</mn><mo>≥</mo><mn>0</mn></math>,则<?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi mathvariant="normal">x</mi></math>的取值范为是(  )<?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mfrac><mrow><mo>-</mo><mi mathvariant="normal">b</mi><mo>±</mo><msqrt><msup><mrow><mi mathvariant="normal">b</mi></mrow><mrow><mn>2</mn></mrow></msup><mo>-</mo><mn>4</mn><mi mathvariant="normal">a</mi><mi mathvariant="normal">c</mi></msqrt></mrow><mrow><mn>2</mn><mi mathvariant="normal">a</mi></mrow></mfrac></math>",
        "questionType":"单选题",
        "rightAnswer":"【答案】A",
        "analysis":"【解析】y-2≥0,y大于等于2.故x≥2",
        "score":null,
        "option":"【选项】A、 x≥2               B、 x>1    C、 x≥0               D、 x≤1",
        "options":{
            "answerA":"A、 x≥2",
            "answerB":"B、 x>1",
            "answerC":"C、 x≥0",
            "answerD":"D、 x≤1"
        }
    },
    {
        "title":"【试卷】学校2020年第二学期期末考试卷",
        "subject":"【科目】数学",
        "number":null,
        "question":"【题文】若<?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi mathvariant="normal">x</mi><mo>≥</mo><mi mathvariant="normal">y</mi></math>,<?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi mathvariant="normal">y</mi><mo>-</mo><mn>2</mn><mo>≥</mo><mn>0</mn></math>,则<?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi mathvariant="normal">x</mi></math>的取值范为是(  )",
        "questionType":"单选题",
        "rightAnswer":"【答案】B",
        "analysis":"【解析】y-2≥0,y大于等于2.故x≥2",
        "score":null,
        "option":"【选项】A、 x≥2               B、 x>1    C、 x≥0               D、 x≤1",
        "options":{
            "answerA":"A、 x≥2",
            "answerB":"B、 x>1",
            "answerC":"C、 x≥0",
            "answerD":"D、 x≤1"
        }
    },
    {
        "title":"【试卷】学校2020年第二学期期末考试卷",
        "subject":"【科目】数学",
        "number":null,
        "question":"【题文】若<?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi mathvariant="normal">x</mi><mo>≥</mo><mi mathvariant="normal">y</mi></math>,<?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi mathvariant="normal">y</mi><mo>-</mo><mn>2</mn><mo>≥</mo><mn>0</mn></math>,则<?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi mathvariant="normal">x</mi></math>取值范为是取值范为是取值范为是取值范为是取值范为是取值范为是取值范为是取值范为是取值范为是取值范为是取值范为是取值范为是取值范为是取值范为是取值范为是(  )
<table  border=1><tr><td>数学公式</td><td>物理公式</td><td>图片</td></tr><tr><td><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi>f</mi><mfenced separators="|"><mrow><mi>x</mi></mrow></mfenced><mo>=</mo><msub><mrow><mi>a</mi></mrow><mrow><mn>0</mn></mrow></msub><mo>+</mo><mrow><msubsup><mo stretchy="true">∑</mo><mrow><mi>n</mi><mo>=</mo><mn>1</mn></mrow><mrow><mi>∞</mi></mrow></msubsup><mrow><mfenced separators="|"><mrow><msub><mrow><mi>a</mi></mrow><mrow><mi>n</mi></mrow></msub><mrow><mrow><mi mathvariant="normal">cos</mi></mrow><mo>⁡</mo><mrow><mfrac><mrow><mi>n</mi><mi>π</mi><mi>x</mi></mrow><mrow><mi>L</mi></mrow></mfrac></mrow></mrow><mo>+</mo><msub><mrow><mi>b</mi></mrow><mrow><mi>n</mi></mrow></msub><mrow><mrow><mi mathvariant="normal">sin</mi></mrow><mo>⁡</mo><mrow><mfrac><mrow><mi>n</mi><mi>π</mi><mi>x</mi></mrow><mrow><mi>L</mi></mrow></mfrac></mrow></mrow></mrow></mfenced></mrow></mrow></math></td><td><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi>F</mi><mo>=</mo><mi>G</mi><mfrac><mrow><msub><mrow><mi>m</mi></mrow><mrow><mn>1</mn></mrow></msub><msub><mrow><mi>m</mi></mrow><mrow><mn>2</mn></mrow></msub></mrow><mrow><msup><mrow><mi>r</mi></mrow><mrow><mn>2</mn></mrow></msup></mrow></mfrac><mo>,</mo><mi mathvariant="normal"> </mi><mi mathvariant="normal"> </mi><mi>G</mi><mo>=</mo><mn>6.67</mn><mo>×</mo><msup><mrow><mn>10</mn></mrow><mrow><mo>-</mo><mn>11</mn></mrow></msup></math></td><td><img src="E:/demo/image_0.jpeg" width="118" height="88" /></td></tr></table>

<img src="E:/demo/image_0.jpeg" width="118" height="88" /><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><msup><mrow><mi>a</mi></mrow><mrow><mn>2</mn></mrow></msup><mo>+</mo><msup><mrow><mi>b</mi></mrow><mrow><mn>2</mn></mrow></msup><mo>=</mo><msup><mrow><mi>c</mi></mrow><mrow><mn>2</mn></mrow></msup></math>",
        "questionType":"单选题",
        "rightAnswer":"【答案】B<?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi>f</mi><mfenced separators="|"><mrow><mi>x</mi></mrow></mfenced><mo>=</mo><msub><mrow><mi>a</mi></mrow><mrow><mn>0</mn></mrow></msub><mo>+</mo><mrow><msubsup><mo stretchy="true">∑</mo><mrow><mi>n</mi><mo>=</mo><mn>1</mn></mrow><mrow><mi>∞</mi></mrow></msubsup><mrow><mfenced separators="|"><mrow><msub><mrow><mi>a</mi></mrow><mrow><mi>n</mi></mrow></msub><mrow><mrow><mi mathvariant="normal">cos</mi></mrow><mo>⁡</mo><mrow><mfrac><mrow><mi>n</mi><mi>π</mi><mi>x</mi></mrow><mrow><mi>L</mi></mrow></mfrac></mrow></mrow><mo>+</mo><msub><mrow><mi>b</mi></mrow><mrow><mi>n</mi></mrow></msub><mrow><mrow><mi mathvariant="normal">sin</mi></mrow><mo>⁡</mo><mrow><mfrac><mrow><mi>n</mi><mi>π</mi><mi>x</mi></mrow><mrow><mi>L</mi></mrow></mfrac></mrow></mrow></mrow></mfenced></mrow></mrow></math>",
        "analysis":"【解析】y-2≥0,y大于等于2.故x≥2",
        "score":null,
        "option":"【选项】A、 x≥2                   B、 x>1    C、 x≥0                  D、 x≤1",
        "options":{
            "answerA":"A、 x≥2",
            "answerB":"B、 x>1",
            "answerC":"C、 x≥0",
            "answerD":"D、 x≤1"
        }
    },
    {
        "title":"【试卷】学校2020年第二学期期末考试卷",
        "subject":"【科目】数学",
        "number":null,
        "question":"【题文】在实数-1、0、1、2中,正数有是(  )<img src="E:/demo/image_0.jpeg" width="118" height="88" />",
        "questionType":"多选题",
        "rightAnswer":"【答案】B、D",
        "analysis":"【解析】正数的基本定义",
        "score":null,
        "option":"【选项】A. -1这是选项换行的部分    B. <?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi>x</mi><mo>=</mo><mfrac><mrow><mo>-</mo><mi>b</mi><mo>±</mo><msqrt><msup><mrow><mi>b</mi></mrow><mrow><mn>2</mn></mrow></msup><mo>-</mo><mn>4</mn><mi>a</mi><mi>c</mi></msqrt></mrow><mrow><mn>2</mn><mi>a</mi></mrow></mfrac></math>C. 0<?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mrow><msub><mo stretchy="false">∭</mo><mrow><mi>V</mi></mrow></msub><mrow><mfenced separators="|"><mrow><mo>∇</mo><mo>⋅</mo><mi mathvariant="bold">F</mi></mrow></mfenced><mo>ⅆ</mo><mi>V</mi></mrow></mrow><mo>=</mo><mrow><msub><mo stretchy="false">∯</mo><mrow><mi>S</mi></mrow></msub><mrow><mi mathvariant="bold">F</mi><mo>⋅</mo><mo>ⅆ</mo><mi mathvariant="bold">S</mi></mrow></mrow></math>    D. 2",
        "options":{
            "answerA":"A. -1这是选项换行的部分",
            "answerB":"B. <?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi>x</mi><mo>=</mo><mfrac><mrow><mo>-</mo><mi>b</mi><mo>±</mo><msqrt><msup><mrow><mi>b</mi></mrow><mrow><mn>2</mn></mrow></msup><mo>-</mo><mn>4</mn><mi>a</mi><mi>c</mi></msqrt></mrow><mrow><mn>2</mn><mi>a</mi></mrow></mfrac></math>",
            "answerC":"C. 0<?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mrow><msub><mo stretchy="false">∭</mo><mrow><mi>V</mi></mrow></msub><mrow><mfenced separators="|"><mrow><mo>∇</mo><mo>⋅</mo><mi mathvariant="bold">F</mi></mrow></mfenced><mo>ⅆ</mo><mi>V</mi></mrow></mrow><mo>=</mo><mrow><msub><mo stretchy="false">∯</mo><mrow><mi>S</mi></mrow></msub><mrow><mi mathvariant="bold">F</mi><mo>⋅</mo><mo>ⅆ</mo><mi mathvariant="bold">S</mi></mrow></mrow></math>",
            "answerD":"D. 2"
        }
    },
    {
        "title":"【试卷】学校2020年第二学期期末考试卷",
        "subject":"【科目】数学",
        "number":null,
        "question":"【题文】As a volunteer crisis counselor with Crisis Text Line, a free hotline, all via text messages, I am very proud. _________ most of the texters who reach out to us are under the age of 25, this activity _________ me with the chance to interact with the _________ generation.【小题1】    A.If    B.Though    C.But    D.Since【小题2】    A.provides    B.equips    C.charges    D.presents【小题3】    A.older    B.younger    C.former    D.later",
        "questionType":"填空题",
        "rightAnswer":"【答案】【小题1】B    【小题2】C    【小题3】A    ",
        "analysis":"【解析】完形填空主要考查考生词汇量以及对语法的认知程度<?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><msup><mrow><mfenced separators="|"><mrow><mn>1</mn><mo>+</mo><mi>x</mi></mrow></mfenced></mrow><mrow><mi>n</mi></mrow></msup><mo>=</mo><mn>1</mn><mo>+</mo><mfrac><mrow><mi>n</mi><mi>x</mi></mrow><mrow><mn>1</mn><mo>!</mo></mrow></mfrac><mo>+</mo><mfrac><mrow><mi>n</mi><mfenced separators="|"><mrow><mi>n</mi><mo>-</mo><mn>1</mn></mrow></mfenced><msup><mrow><mi>x</mi></mrow><mrow><mn>2</mn></mrow></msup></mrow><mrow><mn>2</mn><mo>!</mo></mrow></mfrac><mo>+</mo><mo>…</mo></math>",
        "score":null,
        "option":null,
        "options":null
    },
    {
        "title":"【试卷】学校2020年第二学期期末考试卷",
        "subject":"【科目】数学",
        "number":null,
        "question":"【题文】补写出下列句子中的空缺部分。(1)李贺《雁门太守行》既渲染了兵临城下的紧张气氛,又写出了守军威武雄壮的气势的两句是“__________________,________________”。(2)庄子在《逍遥游》中以大鹏直冲青天须凭借风来表达“做事成功需要有丰厚积累”的道理的两句是“______________,______________”。(3)《琵琶行》中,自居易在描写琵琶女与五陵少年奏乐饮酒的奢华欢愉后,又以“______________,______________”两句高度概括了琵琶女美好年华的悄然而逝。",
        "questionType":"填空题",
        "rightAnswer":"【答案】(1)黑云压城城欲摧    甲光向日金鳞开(2)抟扶摇而上者九万里    去以六月息者也",
        "analysis":"(3)今年欢笑复明年    秋月春风等闲度
【解析】此题考查学生对诗文的背诵、理解、默写能力。考查直接性默写和理解性默写两种类型,此题属于理解性默写。要求学生平时既要注意记忆、积累,同时在此基础上加以理解、应用和赏析。此题中需注意“鳞”“抟”的书写。",
        "score":null,
        "option":null,
        "options":null
    },
    {
        "title":"【试卷】学校2020年第二学期期末考试卷",
        "subject":"【科目】数学",
        "number":null,
        "question":"【题文】研究人员在对某湖泊生态系统进行研究,其中水草、草食鱼、乌鱼、河蟹等构成食物网。请回答下列问题:(1)用标志重捕法调查乌鱼的种群密度时,下列情况不会导致调查结果偏高的是          ​(单选)。A.标志物脱落                        B.标志物过于明显,易被乌鱼天敌发现C.被捕捉过一次后更难被捕捉          D.标志到重捕的时间间隔过短(2)湖泊中某些小型鱼类生活在水体上层,草食性鱼类、乌鱼等生活在水体中层,青鱼等生活在水体下层,这体现了群落在________结构上具有________现象,其原因是不同动物具有不同的________。(3)草食性鱼类的粪便能被细菌和真菌分解,这种情况下,细菌和真菌的能量直接源于第          营养级。该湖泊中肉食性鱼类数量较植食性鱼类数量少,从能量流动特点分析,其原因是________。(4)该湖泊目前已被生活污水污染,为找出理想的污染治理措施,科研人员做了相关研究。如图表示某地排污池下游河道中多种指标的测定结果。请回答:<table  border=1><tr><td>生物</td><td>毒素含量ug/L</td><td>铵盐吸收率</td><td>有机物去除率</td></tr><tr><td>硅藻</td><td>0.1</td><td>52%</td><td>0</td></tr><tr><td>蓝藻</td><td>0.554</td><td>78%</td><td>0</td></tr><tr><td>红假单胞光合菌</td><td>0</td><td>87%</td><td>92%</td></tr></table>注:红假单胞光合菌是一种光合细菌,菌体内有多种光合色素。其有光条件下可进行光能自养生长,无光条件下可分解有机物异养生长。从生态系统的组成成分看,表中红假单胞光合菌属于________。从表中数据分析,三种生物中治理该湖泊污染效果最好的生物是________,理由是________。",
        "questionType":"主观题",
        "rightAnswer":"【答案】(1)D (2)垂直;分层  食物和栖息空间(3)一 ;肉食性鱼类较植食性鱼类营养级高,而能量流动逐级递减(4)生产者和分解者;红假单胞光合菌;不产生毒素且对铵盐和有机物的去除率高",
        "analysis":"【解析】本题结合图解,考查种群、群落以及生态系统的结构和功能,意在考查考生的识图能力和理解所学知识要点,能运用所学知识,准确判断问题的能力,属于考纲识记和理解层次的考查。关于能量流动特点的分析:1.消费者的同化量=消费者的摄入量-粪便量,粪便量是上一营养级的同化量;2.每一营养级同化量=呼吸消耗量+分解者利用量+未被利用量+下一营养级同化量,所以能量流动具有逐级递减的特点。【解答】(1)根据标志重捕法的估算公式:种群数量=(重捕个体数/重捕中标记数)×初次捕获标数记,如果被捕捉过一次后更难被捕捉、标志物脱落或者标志物过于明显,易被乌鱼天敌发现,都会造成重捕中标记个数减少,从而使估算的种群数量偏大于真实数值。而标志到重捕的时间间隔过短,使重捕中标记个数增大,会造成估算数值小于真实数值。故选 D。(2)由于不同鱼的食物和栖息空间不同,造成不同种的鱼生活在不同水层,体现了群落在垂直结构上具有分层现象。(3)被细菌和真菌分解的草食性鱼类的粪便中的能量属于草的同化量,即第一营养级的同化量;该湖泊中肉食性鱼类数量较植食性鱼类数量少,从能量流动特点分析,其原因是肉食性鱼类较植食性鱼类营养级高,而能量流动逐级递减,所以湖泊中能维持生存的肉食性鱼类数量较植食性鱼类数量少。(4)红假单胞光合菌能进行光合作用属于生产者,又能分解有机物异养生长属于分解者;据表格数据分析,治理水华效果最好的生物是红假单胞光合菌,理由是不产毒素且对铵盐和有机物的去除率高。故答案为:(1)D (2)垂直;分层  食物和栖息空间(3)一 ;肉食性鱼类较植食性鱼类营养级高,而能量流动逐级递减 ​(4)生产者和分解者;红假单胞光合菌;不产生毒素且对铵盐和有机物的去除率高",
        "score":null,
        "option":null,
        "options":null
    }
]

3:需要使用poi的maven依赖

       <dependency>
           <groupId>org.apache.poi</groupId>
           <artifactId>poi</artifactId>
           <version>3.15</version>
       </dependency>
       <dependency>
           <groupId>org.apache.poi</groupId>
           <artifactId>poi-scratchpad</artifactId>
           <version>3.15</version>
       </dependency>
       <dependency>
           <groupId>org.apache.poi</groupId>
           <artifactId>poi-ooxml</artifactId>
           <version>3.15</version>
       </dependency>
       <dependency>
       <groupId>org.apache.poi</groupId>
       <artifactId>poi-ooxml-schemas</artifactId>
       <version>3.15</version>

4:保存题目的实体对象

/**
 * @author : ywb
 * @createdDate : 2020/9/21
 * @updatedDate
 */
@Data
public class Question {
    /**
     * 试卷标题
     */
    private String title;

    /**
     * 科目
     */
    private String subject;

    /**
     * 题号
     */
    private Integer number;
    /**
     * 问题
     */
    private String question;
    /**
     * 试题类型
     */
    private String questionType;
    /**
     * 正确答案
     */
    private String rightAnswer;

    /**
     * 解析
     */
    private String analysis;


    /**
     * 分数
     */
    private String score;

    /**
     * ABCDE选项
     */
    private String option;

    /**
     * 选项
     */
    private Options options;


}
/**
 * @author : ywb
 * @createdDate : 2020/9/24
 * @updatedDate
 */
@Data
public class Options implements Serializable {
    //A选项
    private String answerA;
    //B选项
    private String answerB;
    //C选项
    private String answerC;
    //D选项
    private String answerD;
}

5:需要使用的常量枚举类

/**
 * @author : ywb
 * @createdDate : 2020/9/25
 * @updatedDate
 */
public enum QuestionTypeEnum {
    SINGLE_CHOICE_QUESTION(1,"单选题"),
    MULTIPLE_CHOICE(2,"多选题"),
    GAP_FILLING(3,"填空题"),
    RESPONSE_QUESTION(4,"解答题"),
    SUBJECTIVE_ITEM(5,"主观题"),
    TOPIC(6,"【题文】"),
    CHOICE(7,"【选项】"),
    ANSWER(8,"【答案】"),
    ANALYSIS(9,"【解析】"),
    FINISH(10,"【结束】"),
    NOT_CHOICE_QUESTION(11,"非选择题"),
    CHOICE_QUESTION(12,"选择题")
    ;


    private Integer code;
    private String type;

    QuestionTypeEnum(Integer code, String type) {
        this.code = code;
        this.type = type;
    }

    public Integer getCode() {
        return code;
    }

    public String getType() {
        return type;
    }
}

6:保存word文档中的图片到本地文件夹的类

/**
 * 一个保存图片到硬盘上的类
 * @author : ywb
 * @createdDate : 2020/9/16
 * @updatedDate
 */
public class ImageParse {

    int number = 0;
    private String targetDir;
    private String baseUrl;

    public ImageParse(String targetDir, String baseUrl) {
        super();
        this.targetDir = targetDir;
        this.baseUrl = baseUrl;
    }


    public String parse(byte[] data, String extName) {
        return parse(new ByteArrayInputStream(data), extName);
    }


    public String parse(InputStream in, String extName) {
        if (extName.lastIndexOf(".") > -1) {
            extName = extName.substring(extName.lastIndexOf(".") + 1);
        }
        String filename = "image_" + (number++) + "." + extName;
        File target = new File(targetDir);
        if (!target.exists()) {
            target.mkdirs();
        }
        try {
            IOUtils.copy(in, new FileOutputStream(new File(target, filename)));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return baseUrl + filename;
    }
}

7:解析word中存在复杂的表格,表格中包含数学、化学、物理等公式以及图片

/**
 * 将word文档中的表格转成HTML中表格的样式
 *
 * @author : ywb
 * @createdDate : 2020/9/15
 * @updatedDate
 */
public class ReadWordTable {

    /**
     * 打印日志
     */
    private static final Logger log = LoggerFactory.getLogger(ReadWordTable.class);

    /**
     * 保存生成HTML时需要被忽略的单元格
     */
    private List<String> omitCellsList = new ArrayList<String>();

    /**
     * 生成忽略的单元格列表中的格式
     *
     * @param row
     * @param col
     * @return
     */
    public String generateOmitCellStr(int row, int col) {
        return row + ":" + col;
    }

    /**
     * 获取当前单元格的colspan(列合并)的列数
     *
     * @param tcPr 单元格属性
     * @return
     */
    public int getColspan(CTTcPr tcPr) {
        // 判断是否存在列合并
        CTDecimalNumber gridSpan = null;
        // 合并的起始列
        if ((gridSpan = tcPr.getGridSpan()) != null) {
            // 获取合并的列数
            BigInteger num = gridSpan.getVal();
            return num.intValue();
        } else {
            // 其他被合并的列或正常列
            return 1;
        }
    }

    /**
     * 获取当前单元格的rowspan(行合并)的行数
     *
     * @param table 表格
     * @param row   行值
     * @param col   列值
     * @return
     */
    public int getRowspan(XWPFTable table, int row, int col) {

        XWPFTableCell cell = table.getRow(row).getCell(col);
        // 正常独立单元格
        if (!isContinueRow(cell) && !isRestartRow(cell)) {
            return 1;
        }
        // 当前单元格的宽度
        int cellWidth = getCellWidth(table, row, col);
        // 当前单元格距离左侧边框的距离
        int leftWidth = getLeftWidth(table, row, col);

        // 用户保存当前单元格行合并的单元格数-1(因为不包含自身)
        List<Boolean> list = new ArrayList<Boolean>();
        getRowspan(table, row, cellWidth, leftWidth, list);

        return list.size() + 1;
    }

    /**
     *
     * @param table
     * @param row
     * @param cellWidth
     * @param leftWidth
     * @param list
     */
    private void getRowspan(XWPFTable table, int row, int cellWidth, int leftWidth, List<Boolean> list) {
        // 已达到最后一行
        if (row + 1 >= table.getNumberOfRows()) {
            return;
        }
        row = row + 1;
        int colsNum = table.getRow(row).getTableCells().size();
        // 因为列合并单元格可能导致行合并的单元格并不在同一列,所以从头遍历列,通过属性、宽度以及距离左边框间距来判断是否是行合并
        for (int i = 0; i < colsNum; i++) {
            XWPFTableCell testTable = table.getRow(row).getCell(i);
            // 是否为合并单元格的中间行(包括结尾行)
            if (isContinueRow(testTable)) {
                // 是被上一行单元格合并的单元格
                if (getCellWidth(table, row, i) == cellWidth
                        && getLeftWidth(table, row, i) == leftWidth) {
                    list.add(true);
                    // 被合并的单元格在生成html时需要忽略
                    addOmitCell(row, i);
                    // 去下一行继续查找
                    getRowspan(table, row, cellWidth, leftWidth, list);
                    break;
                }
            }
        }
    }

    /**
     * 判断是否是合并行的起始行单元格
     *
     * @param tableCell
     * @return
     */
    public boolean isRestartRow(XWPFTableCell tableCell) {
        CTTcPr tcPr = tableCell.getCTTc().getTcPr();
        if (tcPr.getVMerge() == null) {
            return false;
        }
        if (tcPr.getVMerge().getVal() == null) {
            return false;
        }
        if (tcPr.getVMerge().getVal().toString().equalsIgnoreCase("restart")) {
            return true;
        }
        return false;
    }

    /**
     * 判断是否是合并行的中间行单元格(包括结尾的最后一行的单元格)
     *
     * @param tableCell
     * @return
     */
    public boolean isContinueRow(XWPFTableCell tableCell) {
        CTTcPr tcPr = tableCell.getCTTc().getTcPr();
        if (tcPr.getVMerge() == null) {
            return false;
        }
        if (tcPr.getVMerge().getVal() == null) {
            return true;
        }
        return false;
    }

    /**
     * 获取当前单元格距离左边框的距离
     * @param table
     * @param row
     * @param col
     * @return
     */
    public int getLeftWidth(XWPFTable table, int row, int col) {
        int leftWidth = 0;
        for (int i = 0; i < col; i++) {
            leftWidth += getCellWidth(table, row, i);
        }
        return leftWidth;
    }

    /**
     * 获取当前单元格的宽度
     * @param table
     * @param row
     * @param col
     * @return
     */
    public int getCellWidth(XWPFTable table, int row, int col) {
        BigInteger width = table.getRow(row).getCell(col).getCTTc().getTcPr().getTcW().getW();
        return width.intValue();
    }

    /**
     * 添加忽略的单元格(被行合并的单元格,生成HTML时需要忽略)
     *
     * @param row
     * @param col
     */
    public void addOmitCell(int row, int col) {
        String omitCellStr = generateOmitCellStr(row, col);
        omitCellsList.add(omitCellStr);
    }

    public boolean isOmitCell(int row, int col) {
        String cellStr = generateOmitCellStr(row, col);
        return omitCellsList.contains(cellStr);
    }

    /**
     * 读取表格中的内容包含图片以及各个科目的公式
     *
     * @param table      表格的XWPFTable对象
     * @param imageParse 图片保存到本地
     * @return
     */
    public String readTable(XWPFTable table, ImageParse imageParse) {
        log.info("<< 解析段落中的表格");
        // 表格行数
        int tableRowsSize = table.getRows().size();
        StringBuilder tableToHtmlStr = new StringBuilder("<table  border=1>");

        for (int i = 0; i < tableRowsSize; i++) {
            tableToHtmlStr.append("<tr>");

            int tableCellsSize = table.getRow(i).getTableCells().size();
            for (int j = 0; j < tableCellsSize; j++) {
                if (isOmitCell(i, j)) {
                    continue;
                }
                XWPFTableCell tableCell = table.getRow(i).getCell(j);
                // 获取单元格的属性
                CTTcPr tcPr = tableCell.getCTTc().getTcPr();
                int colspan = getColspan(tcPr);
                if (colspan > 1) {
                    // 合并的列
                    tableToHtmlStr.append("<td colspan='" + colspan + "'");
                } else {
                    // 正常列
                    tableToHtmlStr.append("<td");
                }

                int rowspan = getRowspan(table, i, j);
                // 合并的行
                if (rowspan > 1) {
                    tableToHtmlStr.append(" rowspan='" + rowspan + "'>");
                } else {
                    tableToHtmlStr.append(">");
                }

                List<XWPFParagraph> paragraphs = tableCell.getParagraphs();
                StringBuilder stringBuilder = null;
                for (XWPFParagraph paragraph : paragraphs) {
                    //获取单元格中的数据
                    stringBuilder = new StringBuilder();
                    //解析表格单元格中存在的特殊公式以及表格的单元格中存在的图片
                    ParagraphHandle.handleParagraph(stringBuilder, paragraph, imageParse);
                }

                tableToHtmlStr.append(stringBuilder.toString() + "</td>");
            }
            tableToHtmlStr.append("</tr>");
        }
        tableToHtmlStr.append("</table>");

        clearTableInfo();

        return tableToHtmlStr.toString();
    }

    /**
     * 清除cellList
     */
    public void clearTableInfo() {
        omitCellsList.clear();
    }

例如:

<table  border=1><tr><td>1one</td><td colspan='2'>数学公式</td><td>Three</td><td>Four</td><td>Six</td><td>Seven</td><td>eight</td></tr><tr><td rowspan='26'>这是一个公告 </td><td rowspan='16'><img src="E:/demo/image_0.png" width="34" height="117" /></td><td rowspan='6'><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi>x</mi><mo>=</mo><mfrac><mrow><mo>-</mo><mi>b</mi><mo>±</mo><msqrt><msup><mrow><mi>b</mi></mrow><mrow><mn>2</mn></mrow></msup><mo>-</mo><mn>4</mn><mi>a</mi><mi>c</mi></msqrt></mrow><mrow><mn>2</mn><mi>a</mi></mrow></mfrac></math></td><td>1</td><td rowspan='26'><img src="E:/demo/image_1.png" width="34" height="117" /></td><td rowspan='26'></td><td rowspan='26'></td><td rowspan='4'></td></tr><tr><td></td></tr><tr><td>2</td></tr><tr><td>3</td></tr><tr><td>4</td><td rowspan='2'></td></tr><tr><td>5</td></tr><tr><td rowspan='10'><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><msup><mrow><mfenced separators="|"><mrow><mi>x</mi><mo>+</mo><mi>a</mi></mrow></mfenced></mrow><mrow><mi>n</mi></mrow></msup><mo>=</mo><mrow><msubsup><mo stretchy="true">∑</mo><mrow><mi>k</mi><mo>=</mo><mn>0</mn></mrow><mrow><mi>n</mi></mrow></msubsup><mrow><mfenced separators="|"><mrow><mfrac linethickness="0pt"><mrow><mi>n</mi></mrow><mrow><mi>k</mi></mrow></mfrac></mrow></mfenced><msup><mrow><mi>x</mi></mrow><mrow><mi>k</mi></mrow></msup><msup><mrow><mi>a</mi></mrow><mrow><mi>n</mi><mo>-</mo><mi>k</mi></mrow></msup></mrow></mrow></math></td><td>6</td><td></td></tr><tr><td>7</td><td rowspan='4'></td></tr><tr><td>8</td></tr><tr><td>9</td></tr><tr><td>0</td></tr><tr><td>11</td><td rowspan='2'></td></tr><tr><td>12</td></tr><tr><td>1</td><td rowspan='3'></td></tr><tr><td>1</td></tr><tr><td>1</td></tr><tr><td rowspan='9'></td><td rowspan='9'><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><msup><mrow><mi>e</mi></mrow><mrow><mi>x</mi></mrow></msup><mo>=</mo><mn>1</mn><mo>+</mo><mfrac><mrow><mi>x</mi></mrow><mrow><mn>1</mn><mo>!</mo></mrow></mfrac><mo>+</mo><mfrac><mrow><msup><mrow><mi>x</mi></mrow><mrow><mn>2</mn></mrow></msup></mrow><mrow><mn>2</mn><mo>!</mo></mrow></mfrac><mo>+</mo><mfrac><mrow><msup><mrow><mi>x</mi></mrow><mrow><mn>3</mn></mrow></msup></mrow><mrow><mn>3</mn><mo>!</mo></mrow></mfrac><mo>+</mo><mo>…</mo><mo>,</mo><mi mathvariant="normal"> </mi><mi mathvariant="normal"> </mi><mo>-</mo><mi>∞</mi><mo>&lt;</mo><mi>x</mi><mo>&lt;</mo><mi>∞</mi></math></td><td>11</td><td></td></tr><tr><td>1</td><td rowspan='9'></td></tr><tr><td>12</td></tr><tr><td>3</td></tr><tr><td>4</td></tr><tr><td>5</td></tr><tr><td>6</td></tr><tr><td>7</td></tr><tr><td>8</td></tr><tr><td colspan='2'></td><td>9</td></tr><tr><td>好的</td><td colspan='2'>好的</td><td>好的</td><td>好的</td><td></td><td></td><td></td></tr></table>

8:poi读取word时,XWPFParagraph 有两个方法可以分别提出XWPFRun和CTOMath,但是不知道其所在的位置,此类解决了这个问题

/**
 * 可以把公式和XWPFRun按顺序遍历,用到了以CTP开头的底层方式
 * <p>
 * XWPFParagraph中主有两个方法可以分别提出XWPFRun和CTOMath,但是不知道位置
 * ParagraphChildOrderManager这个类是专门解决这个问题的
 *
 * @author : ywb
 * @createdDate : 2020/9/16
 * @updatedDate
 */
public class ParagraphChildOrderManager {

    public static int TYPE_RUN = 1;
    public static int TYPE_OMATH = 2;

    List<Integer> typeList = new ArrayList<Integer>();
    XWPFParagraph p;

    public ParagraphChildOrderManager(XWPFParagraph paragraph) {
        this.p = paragraph;
        //using a cursor to go through the paragraph from top to down
        XmlCursor xmlcursor = paragraph.getCTP().newCursor();
        while (xmlcursor.hasNextToken()) {
            XmlCursor.TokenType tokenType = xmlcursor.toNextToken();
            if (tokenType.isStart()) {
                if (xmlcursor.getName().getPrefix().equalsIgnoreCase("w") && xmlcursor.getName().getLocalPart().equalsIgnoreCase("r")) {
                    typeList.add(TYPE_RUN);
                } else if (xmlcursor.getName().getLocalPart().equalsIgnoreCase("oMath")) {
                    typeList.add(TYPE_OMATH);
                }
            } else if (tokenType.isEnd()) {
                xmlcursor.push();
                xmlcursor.toParent();
                if (xmlcursor.getName().getLocalPart().equalsIgnoreCase("p")) {
                    break;
                }
                xmlcursor.pop();
            }
        }
    }

    public List<Object> getChildList() {
        List<Object> runsOrMathList = new ArrayList<Object>();
        List<XWPFRun> runs = p.getRuns();
        List<CTOMath> oMathList = p.getCTP().getOMathList();

//        Queue<Object> mathQueue = null;
        if (oMathList != null && oMathList.size() > 0) {
            Queue<XWPFRun> runsQueue = new LinkedList<XWPFRun>(runs);
            Queue<CTOMath> mathQueue = new LinkedList<CTOMath>(oMathList);

            for (int i = 0; i < typeList.size(); i++) {
                Integer type = typeList.get(i);
                if (type.equals(TYPE_RUN) && runs.size() > 0) {

                    runsOrMathList.add(runsQueue.poll());
                } else if (type.equals(TYPE_OMATH) && mathQueue.size() > 0) {
                    runsOrMathList.add(mathQueue.poll());
                }
            }
            return runsOrMathList;
        } else {
            List<CTOMathPara> oMathParaList = p.getCTP().getOMathParaList();
            Queue<XWPFRun> runsQueue = new LinkedList<XWPFRun>(runs);
            Queue<CTOMathPara> mathQueue = new LinkedList<CTOMathPara>(oMathParaList);
            for (int i = 0; i < typeList.size(); i++) {
                Integer type = typeList.get(i);
                if (type.equals(TYPE_RUN) && runs.size() > 0) {
                    runsOrMathList.add(runsQueue.poll());
                } else if (type.equals(TYPE_OMATH) && mathQueue.size() > 0) {
                    runsOrMathList.add(mathQueue.poll());
                }
            }
            return runsOrMathList;
        }
    }
}

9:将word文档中公式格式为omML转换为mathML

    /**
     * 将word文档中公式格式为omML转换为mathMl
     * @param omML
     * @return
     */
    public static String getMathMLFromNode(String omML) {
        //通过word中的xsl转成器解析omml公式格式
        StreamSource xslSource = new StreamSource(new File("src/main/resources/OMML2MML.XSL"));
        StringWriter writer = new StringWriter();
        try {
            Transformer t = TransformerFactory.newInstance().newTransformer(xslSource);
            Source sources = new StreamSource(new StringReader(omML));
            StreamResult result = new StreamResult(writer);
            t.transform(sources, result);
        } catch (TransformerException e) {
            e.printStackTrace();
        }
        String mathML = writer.getBuffer().toString();
        mathML = mathML.replaceAll("xmlns:m=\"http://schemas.openxmlformats.org/officeDocument/2006/math\"", "");
        mathML = mathML.replaceAll("xmlns:mml", "xmlns");
        mathML = mathML.replaceAll("mml:", "");
        return mathML;
    }

10:解析word处理类

/**
 * @author : ywb
 * @createdDate : 2020/9/18
 * @updatedDate
 */
public class ParagraphHandle {

    private static final Logger log = LoggerFactory.getLogger(ParagraphHandle.class);

    // 该正则用来匹配一个大题(例如:二、多选题)
    private static String regex = "([一|二|三|四|五|六|七|八|九|十]{1,3})([、.]{1})([\\u4E00-\\u9FA5\\s]+题)";

    public static void main(String[] args) throws IOException {
        wordParser();
    }/**
     * 根据poi将word文档逐级解析成实体对象
     *
     * @throws IOException
     */
    public static List<Question> wordParser() throws IOException {

        ArrayList<Question> list = new ArrayList<Question>();
        //第二种方式使用poi行读的方式逐步解析word文档拼接成实体对象
        XWPFDocument doc = new XWPFDocument(POIXMLDocument.openPackage("C:\\Users\\ywb\\Desktop\\试题分类.docx"));
//        List<XWPFParagraph> paragraphs = doc.getParagraphs();
        List<IBodyElement> paragraphs = doc.getBodyElements();

        System.out.println(paragraphs.size());
        //第一段固定为试卷名称
//        String title = ((XWPFParagraph) paragraphs.get(0)).getParagraphText();
        //第二段固定为什么类型的试卷
//        String subject = ((XWPFParagraph) paragraphs.get(1)).getParagraphText();

        for (int i = 2; i < paragraphs.size(); i++) {
            String paragraphText = ((XWPFParagraph) paragraphs.get(i)).getParagraphText();
            if (paragraphText.contains(QuestionTypeEnum.SINGLE_CHOICE_QUESTION.getType())) {
                int num = getChoiceQuestionList(i, QuestionTypeEnum.SINGLE_CHOICE_QUESTION.getType(), paragraphs, list);
                i = num;
            } else if (paragraphText.contains(QuestionTypeEnum.MULTIPLE_CHOICE.getType())) {
                int num = getChoiceQuestionList(i, QuestionTypeEnum.MULTIPLE_CHOICE.getType(), paragraphs, list);
                i = num;
            } else if (paragraphText.contains(QuestionTypeEnum.GAP_FILLING.getType())) {
                int num = getNotChoiceQuestionList(i, QuestionTypeEnum.GAP_FILLING.getType(), paragraphs, list);
                i = num;
            } else if (paragraphText.contains(QuestionTypeEnum.SUBJECTIVE_ITEM.getType())) {
                int num = getNotChoiceQuestionList(i, QuestionTypeEnum.SUBJECTIVE_ITEM.getType(), paragraphs, list);
                i = num;
            }
        }
        System.out.println(list);
        return list;
    }


    /**
     * 选择题的解析方法
     *
     * @param i
     * @param paragraphs
     * @return
     * @throws IOException
     */
    public static int getChoiceQuestionList(int i, String questionType, List<IBodyElement> paragraphs, List<Question> list) throws IOException {
        //该正则用来匹配一个大题(例如:二、多选题)
        Pattern compile = Pattern.compile(regex);
        i++;
        for (i = i; i < paragraphs.size(); i++) {
            String question = getContent(paragraphs.get(i), new StringBuilder());
            // 表示一种题型的结束
            if (compile.matcher(question).find()) {
                i--;
                break;
            }
            //获取word试卷题目中的题干信息
            if (question.contains(QuestionTypeEnum.TOPIC.getType())) {
                i++;
                for (i = i; i < paragraphs.size(); i++) {
                    String content = getContent(paragraphs.get(i), new StringBuilder());
                    if (content.contains(QuestionTypeEnum.CHOICE.getType())) {
                        break;
                    }
                    question += content;
                }
            }
            //获取题目中的选项信息
            String option = getContent(paragraphs.get(i), new StringBuilder());
            if (option.contains(QuestionTypeEnum.CHOICE.getType())) {
                i++;
                //如果标签不是以【答案】结尾,该题目为多行一题
                while (!((XWPFParagraph) paragraphs.get(i)).getParagraphText().contains(QuestionTypeEnum.ANSWER.getType())) {
                    String content = getContent(paragraphs.get(i), new StringBuilder());

                    option += content;
                    i++;
                }
            }
            //获取题目的答案信息
            String answer = getContent(paragraphs.get(i), new StringBuilder());
            if (answer.contains(QuestionTypeEnum.ANSWER.getType())) {
                i++;
                while (!((XWPFParagraph) paragraphs.get(i)).getParagraphText().contains(QuestionTypeEnum.ANALYSIS.getType())) {
                    String content = getContent(paragraphs.get(i), new StringBuilder());
                    answer += content;
                    i++;
                }
            }
            //获取题目的解析信息
            String analysis = getContent(paragraphs.get(i), new StringBuilder());
            if (analysis.contains(QuestionTypeEnum.ANALYSIS.getType())) {
                i++;
                while (!((XWPFParagraph) paragraphs.get(i)).getParagraphText().contains(QuestionTypeEnum.FINISH.getType())) {
                    String content = getContent(paragraphs.get(i), new StringBuilder());
                    analysis += content;
                    i++;
                }
            }

            if (StringUtils.isNotBlank(question) && StringUtils.isNotBlank(analysis) && StringUtils.isNotBlank(answer) && StringUtils.isNotBlank(option)) {
                Question q = new Question();
                //第一段固定为试卷名称
                String title = ((XWPFParagraph) paragraphs.get(0)).getParagraphText();
                //第二段固定为什么类型的试卷
                String subject = ((XWPFParagraph) paragraphs.get(1)).getParagraphText();
                q.setTitle(title);
                q.setSubject(subject);
                q.setQuestionType(questionType);
                q.setQuestion(question);
                q.setAnalysis(analysis);
                q.setRightAnswer(answer);
                q.setOption(option);
                Options options = divideOption(option);
                q.setOptions(options);
                list.add(q);
            }
        }
        System.out.println(i);
        return i;
    }
/**
     * 将选项分隔出来
     *
     * @param option
     * @return
     */
    public static Options divideOption(String option) {
        //使用subString将答案分割
        String regex = "[A|B|C|D]、";
        Options options = new Options();
        String optionA = option.substring(option.indexOf("A"), option.indexOf("B"));
        String optionB = option.substring(option.indexOf("B"), option.indexOf("C"));
        String optionC = option.substring(option.indexOf("C"), option.indexOf("D"));
        String optionD = option.substring(option.indexOf("D"));
        options.setAnswerA(optionA);
        options.setAnswerB(optionB);
        options.setAnswerC(optionC);
        options.setAnswerD(optionD);
        return options;
    }/**
     *  段落处理器
     * @param content 拼接word内容
     * @param body word段落
     * @param imageParser 保存图片文件到磁盘
     */
    public static void handleParagraph(StringBuilder content, IBodyElement body, ImageParse imageParser) {
        log.info(">> 开始解析段落");
        XWPFParagraph p = (XWPFParagraph) body;
        if (p.isEmpty() || p.isWordWrap() || p.isPageBreak()) {
            return;
        }
//        String tagName = "p";
//        content.append("<" + tagName + ">");
      /*XWPFParagraph 有两个方法可以分别提出XWPFRun和CTOMath,但是不知道位置
      ParagraphChildOrderManager这个类是专门解决这个问题的
      */
        ParagraphChildOrderManager runOrMaths = new ParagraphChildOrderManager(p);
        List<Object> childList = runOrMaths.getChildList();

        for (Object child : childList) {
            if (child instanceof XWPFRun) {
                //处理段落中的文本以及图片
                handleParagraphRun(content, (XWPFRun) child, imageParser);
            } else if (child instanceof CTOMath) {
                // 处理word中存在的公式成mathML格式
                handleParagraphOMath(content, (CTOMath) child);
            } else if (child instanceof CTOMathPara) {
                //处理word中存在的公式
                handleParagraphOMath(content, (CTOMathPara) child);
            }
        }
//        content.append("</" + tagName + ">");
    }


    /**
     * 段落文本处理器
     *
     * @param content
     * @param run
     * @param imageParser
     */
    private static void handleParagraphRun(StringBuilder content, XWPFRun run, ImageParse imageParser) {
        // 有内嵌的图片
        List<XWPFPicture> pics = run.getEmbeddedPictures();
        if (pics != null && pics.size() > 0) {
            handleParagraphRunsImage(content, pics, imageParser);
        } else {
            //纯文本直接获取
            content.append(run.toString());
        }
    }

    /**
     * 处理图片
     *
     * @param content
     * @param pics
     * @param imageParser
     */
    private static void handleParagraphRunsImage(StringBuilder content, List<XWPFPicture> pics, ImageParse imageParser) {
        log.info(">> 开始解析段落中存在的图片");
        for (XWPFPicture pic : pics) {
            log.info(pic.getDescription());
            String path = imageParser.parse(pic.getPictureData().getData(),
                    pic.getPictureData().getFileName());
            log.debug("pic.getPictureData().getFileName()===" + pic.getPictureData().getFileName());

            CTPicture ctPicture = pic.getCTPicture();
            Node domNode = ctPicture.getDomNode();

            Node extNode = W3cNodeUtil.getChildChainNode(domNode, "pic:spPr", "a:ext");
            NamedNodeMap attributes = extNode.getAttributes();
            if (attributes != null && attributes.getNamedItem("cx") != null) {
                int width = WordMyUnits.emuToPx(new Double(attributes.getNamedItem("cx").getNodeValue()));
                int height = WordMyUnits.emuToPx(new Double(attributes.getNamedItem("cy").getNodeValue()));
                content.append(String.format("<img src=\"%s\" width=\"%d\" height=\"%d\" />", path, width, height));
            } else {
                content.append(String.format("<img src=\"%s\" />", path));
            }
        }
    }


    /**
     * 处理公式
     *
     * @param content
     * @param child
     */
    private static void handleParagraphOMath(StringBuilder content, CTOMath child) {
        log.info(">> 开始解析段落中的CTOMath公式");
        //将word中的omml格式转换成mathML
        String mathMLFromNode = OmmlUtils.getMathMLFromNode(child.xmlText());
        content.append(mathMLFromNode);
    }


    /**
     * 处理公式(将word中的omML格式的公式转换成mathML格式的工单)
     *
     * @param content
     * @param child
     */
    private static void handleParagraphOMath(StringBuilder content, CTOMathPara child) {
        log.info(">> 开始解析段落中的CTOMathPara公式");
        //将word中的omml格式转换成mathML
        String mathMLFromNode = OmmlUtils.getMathMLFromNode(child.xmlText());
        content.append(mathMLFromNode);
    }
}

11:前端再调用MathJax.js来进行页面渲染(Html文件引用<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>

vue.js+element ui项目中、package-lock.json中引用mathjax.js

    "mathjax": {
      "version": "2.7.9",
      "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-2.7.9.tgz",
      "integrity": "sha512-NOGEDTIM9+MrsqnjPEjVGNx4q0GQxqm61yQwSK+/5S59i26wId5IC5gNu9/bu8+CCVl5p9G2IHcAl/wJa+5+BQ=="
    },

Html文件引用<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML">渲染页面

<html>
    <head>
        <style>
            p{margin-top:0pt;margin-bottom:1pt;}
            span.a{font-family:'Cambria Math';}
            p.a3{text-align:center;}
            span.a3{font-size:9.0pt;}
            span.Char{font-family:'Cambria Math';font-size:9.0pt;}
            span.a4{font-size:9.0pt;}
            span.Char0{font-family:'Cambria Math';font-size:9.0pt;}
            span.a5{font-size:9.0pt;}
            span.Char1{font-family:'Cambria Math';font-size:9.0pt;}
            span.a6{font-family:'Cambria Math';font-size:11.0pt;}
            span.Char2{font-family:'Cambria Math';font-size:11.0pt;}
            p.a7{text-indent:21.0pt;}
            span.a8{font-family:'Cambria Math';font-style:italic;color:#808080;}
            span.a9{font-family:'Cambria Math';}
        </style>
        
        <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
    </head>
    <body>

<p><span style='color:#000000;'>【试卷】</span><span style='color:#000000;'>三</span><span style='color:#000000;'>学校</span><span style='color:#000000;'>2020</span><span style='color:#000000;'>年</span><span style='color:#000000;'>第</span><span style='color:#000000;'>二</span><span style='color:#000000;'>学期</span><span style='color:#000000;'>期末考试卷</span></p><p><span style='color:#000000;'>【科目】数学</span></p><p><span style='color:#000000;'>一</span><span style='color:#000000;'>、单选题</span></p><p><span style='color:#000000;'>【题文】</span><span style='color:#000000;'>若</span><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi mathvariant="normal">x</mi><mo>≥</mo><mi mathvariant="normal">y</mi></math><span style='color:#000000;'>,</span><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi mathvariant="normal">y</mi><mo>-</mo><mn>2</mn><mo>≥</mo><mn>0</mn></math><span style='color:#000000;'>,</span><span style='color:#000000;'>则</span><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi mathvariant="normal">x</mi></math><span style='color:#000000;'>的</span><span style='color:#000000;'>取值范为是</span><span style='color:#000000;'>(</span><span style='color:#000000;'>  </span><span style='color:#000000;'>)</span></p><p><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mfrac><mrow><mo>-</mo><mi mathvariant="normal">b</mi><mo>±</mo><msqrt><msup><mrow><mi mathvariant="normal">b</mi></mrow><mrow><mn>2</mn></mrow></msup><mo>-</mo><mn>4</mn><mi mathvariant="normal">a</mi><mi mathvariant="normal">c</mi></msqrt></mrow><mrow><mn>2</mn><mi mathvariant="normal">a</mi></mrow></mfrac></math></p><p><span style='color:#000000;'>【选项】</span><span style='color:#000000;'>A</span><span style='color:#000000;'>、</span><span style='color:#000000;'> </span><span style='color:#000000;'>x≥2</span><span style='color:#000000;'>    </span><span style='color:#000000;'>           </span><span style='color:#000000;'>B</span><span style='color:#000000;'>、</span><span style='color:#000000;'> </span><span style='color:#000000;'>x</span><span style='color:#000000;'>></span><span style='color:#000000;'>1</span><span style='color:#000000;'>    </span></p><p><span style='color:#000000;'>C</span><span style='color:#000000;'>、</span><span style='color:#000000;'> </span><span style='color:#000000;'>x≥0</span><span style='color:#000000;'>    </span><span style='color:#000000;'>           </span><span style='color:#000000;'>D</span><span style='color:#000000;'>、</span><span style='color:#000000;'> </span><span style='color:#000000;'>x≤1</span></p><p><span style='color:#000000;'>【答案】</span><span style='color:#000000;'>A</span></p><p><span style='color:#000000;'>【解析】</span><span style='color:#000000;'>y-2≥0</span><span style='color:#000000;'>,</span><span style='color:#000000;'>y</span><span style='color:#000000;'>大于等于</span><span style='color:#000000;'>2.</span><span style='color:#000000;'>故</span><span style='color:#000000;'>x≥2</span></p><p><span style='color:#000000;'>【结束】</span></p><p><span style='color:#000000;'>二</span><span style='color:#000000;'>、多选题</span></p><p><span style='color:#000000;'>【题文】</span><span style='color:#000000;'>在实数-</span><span style='color:#000000;'>1</span><span style='color:#000000;'>、</span><span style='color:#000000;'>0</span><span style='color:#000000;'>、</span><span style='color:#000000;'>1</span><span style='color:#000000;'>、</span><span style='color:#000000;'>2</span><span style='color:#000000;'>中,</span>正数<span style='color:#000000;'>有是</span><span style='color:#000000;'>(</span><span style='color:#000000;'>  </span><span style='color:#000000;'>)</span></p><p><img src="E:/demo/image_0.jpeg" width="118" height="88" /></p><p><span style='color:#000000;'>【选项】</span><span style='color:#000000;'>A</span><span style='color:#000000;'>.</span><span style='color:#000000;'> </span><span style='color:#000000;'>-1</span></p><p><span style='color:#000000;'>这是</span><span style='color:#000000;'>选项换行的部分</span><span style='color:#000000;'>    </span></p><p><span style='color:#000000;'>B</span><span style='color:#000000;'>. </span><span style='color:#000000;'>1</span><span style='color:#000000;'>    </span></p><p><span style='color:#000000;'>C</span><span style='color:#000000;'>. </span><span style='color:#000000;'>0</span><span style='color:#000000;'>    </span><span style='color:#000000;'>    </span></p><p><span style='color:#000000;'>D</span><span style='color:#000000;'>. </span><span style='color:#000000;'>2</span></p><p><span style='color:#000000;'>【答案】</span><span style='color:#4472c4;'>B</span><span style='color:#4472c4;'>、</span><span style='color:#4472c4;'>D</span></p><p><span style='color:#000000;'>【解析】正数的基本定义</span></p><p><span style='color:#000000;'>【结束】</span></p><p><span style='color:#000000;'>三</span><span style='color:#000000;'>、判断题</span></p><p><span style='color:#000000;'>【题文】把</span><span style='color:#000000;'>2a-2c</span><span style='color:#000000;'>分解因式为</span><span style='color:#000000;'>2(a-c)</span><span style='color:#000000;'>。</span><span style='color:#000000;'>(</span><span style='color:#000000;'>    </span><span style='color:#000000;'>)</span></p><table  border=1><tr><td><p><span style='color:#000000;'>A</span></p></td><td><p><span style='color:#000000;'>1</span></p></td><td><p><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><msqrt><mn>2</mn></msqrt></math></p></td><td><p><img src="E:/demo/image_1.jpeg" width="42" height="32" /></p></td></tr></table><p><span style='color:#000000;'>【答案】</span><span style='color:#000000;'>对</span></p><p><span style='color:#000000;'>【解析】提取公因式法分解因式</span></p><p><span style='color:#000000;'>【结束】</span></p><p><span style='color:#000000;'>四</span><span style='color:#000000;'>、</span><span style='color:#000000;'>填空题</span></p><p><span style='color:#000000;'>【题文】</span><span style='color:#000000;'>大漠孤烟直</span><span style='color:#000000;'>,</span><span style='color:#000000;'><span style='text-decoration: underline;'>[</span></span><span style='color:#000000;'>              </span><span style='color:#000000;'><span style='text-decoration: underline;'>]</span></span><span style='color:#000000;'>。</span><span style='color:#000000;'>  --</span><span style='color:#000000;'>王维《使至塞上》</span></p><p><span style='color:#000000;'>【答案】</span><span style='color:#000000;'>长河</span><span style='color:#000000;'>落日圆</span><span style='color:#000000;'> </span></p><p><span style='color:#000000;'>【解析】</span></p><p><span style='color:#000000;'>【结束】</span></p><p><span style='color:#000000;'>五</span><span style='color:#000000;'>、主观题</span></p><p><span style='color:#000000;'>【题文】</span><span style='color:#000000;'>闻一多</span><span style='color:#000000;'>先生的说和做观后感</span></p><p><span style='color:#000000;'>闻一多先生的说和做观后感</span></p><p><span style='color:#000000;'>【答案】</span></p><p><span style='color:#000000;'>【解析】</span><span style='color:#000000;'>这篇文章记叙了闻一多先生的主要事迹,表现了他的崇高品格,高度赞扬了他的革命精神</span></p><p><span style='color:#000000;'>【结束】</span></p><p><span style='color:#000000;'>【</span><span style='color:#000000;'>题</span><span style='color:#000000;'>文</span><span style='color:#000000;'>】如图所示,电源电压保持不变,断开开关</span><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><msub><mrow><mi mathvariant="normal">S</mi></mrow><mrow><mn>3</mn></mrow></msub></math><span style='color:#000000;'>,分别只闭合</span><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><msub><mrow><mi mathvariant="normal">S</mi></mrow><mrow><mn>1</mn></mrow></msub></math><span style='color:#000000;'>和只闭合</span><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><msub><mrow><mi>S</mi></mrow><mrow><mn>2</mn></mrow></msub></math><span style='color:#000000;'>,电阻</span><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><msub><mrow><mi>R</mi></mrow><mrow><mn>1</mn></mrow></msub></math><span style='color:#000000;'>和</span><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><msub><mrow><mi>R</mi></mrow><mrow><mn>2</mn></mrow></msub></math><span style='color:#000000;'>两端的电压之比为</span><span style='color:#000000;'>4</span><span style='color:#000000;'>:</span><span style='color:#000000;'>3</span><span style='color:#000000;'>,电阻</span><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><msub><mrow><mi>R</mi></mrow><mrow><mn>3</mn></mrow></msub></math><span style='color:#000000;'>两端的电压之比为</span><span style='color:#000000;'>2</span><span style='color:#000000;'>:</span><span style='color:#000000;'>3</span><span style='color:#000000;'>。只闭合</span><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><msub><mrow><mi>S</mi></mrow><mrow><mn>2</mn></mrow></msub></math><span style='color:#000000;'>时</span><span style='color:#000000;'>它</span><span style='color:#000000;'>的</span><span style='color:#000000;'>电阻</span><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><msub><mrow><mi>R</mi></mrow><mrow><mn>3</mn></mrow></msub></math><span style='color:#000000;'>消耗的电功率为</span><span style='color:#000000;'>100W</span><span style='color:#000000;'>。求:</span><span style='color:#000000;'>(</span><span style='color:#000000;'>大题</span><span style='color:#000000;'>如果有图片,请将其放置在大题题文后一行,图片</span><span style='color:#000000;'>布局</span><span style='color:#000000;'>选择</span><span style='color:#000000;'>嵌入型</span><span style='color:#000000;'>)</span><b> </b></p><p><span style='color:#000000;'>【作图】</span><img src="E:/demo/image_2.png" width="180" height="136" /></p><p><span style='color:#000000;'>1.</span><span style='color:#000000;'>只闭合</span><span style='color:#000000;'>S1</span><span style='color:#000000;'>时,电阻</span><span style='color:#000000;'>R1</span><span style='color:#000000;'>和</span><span style='color:#000000;'>R3</span><span style='color:#000000;'>消耗的电功率之比。</span></p><p>2.开关S<sub>1</sub>、S<sub>2</sub>、S<sub>3</sub>都闭合时,电路消耗的总功率。</p><p><span style='color:#000000;'>【</span><span style='color:#000000;'>答案</span><span style='color:#000000;'>】</span><span style='color:#000000;'> </span></p><p><span style='color:#000000;'>【解析】</span></p><p><span style='color:#000000;'>【结束】</span></p><p><span style='color:#000000;'>六</span><span style='color:#000000;'>、英语题</span></p><p><span style='color:#000000;'>【题文】</span><span style='color:#000000;'>What's your name</span><span style='color:#000000;'>?</span></p><p><span style='color:#000000;'>【</span><span style='color:#000000;'>答案</span><span style='color:#000000;'>】请问你叫什么名字?</span><span style='color:#000000;'> </span></p><p><span style='color:#000000;'>【解析】</span></p><p><span style='color:#000000;'>【结束】</span></p><p><span style='color:#000000;'>七</span><span style='color:#000000;'>、</span><span style='color:#000000;'>完形填空题</span></p><p>【题文】As a volunteer crisis counselor with Crisis Text Line, a free hotline, all via text messages, I am very proud. _________ most of the texters who reach out to us are under the age of 25, this activity _________ me with the chance to interact with the _________ generation.</p><p><span style='color:#4472c4;'>【小题</span><span style='color:#4472c4;'>1】</span>    A.If    B.Though    C.But    D.Since</p><p><span style='color:#4472c4;'>【小题2】</span>    A.provides    B.equips    C.charges    D.presents</p><p><span style='color:#4472c4;'>【小题3】</span>    A.older    B.younger    C.former    D.later</p><p><span style='color:#000000;'>【答案】</span></p><p><span style='color:#4472c4;'>【小题</span><span style='color:#4472c4;'>1</span><span style='color:#4472c4;'>】</span><span style='color:#4472c4;'>B</span><span style='color:#4472c4;'>    </span><span style='color:#4472c4;'>【小题</span><span style='color:#4472c4;'>2</span><span style='color:#4472c4;'>】</span><span style='color:#4472c4;'>C</span><span style='color:#4472c4;'>    </span><span style='color:#4472c4;'>【小题</span><span style='color:#4472c4;'>3</span><span style='color:#4472c4;'>】</span><span style='color:#4472c4;'>A</span><span style='color:#4472c4;'>    </span></p><p><span style='color:#000000;'>【解析】</span></p><p><span style='color:#000000;'>【结束】</span></p><p><span style='color:#000000;'>八、</span><span style='color:#000000;'>物理题</span></p><p><span style='color:#000000;'>【题文】如图所示,物块</span><span style='color:#000000;'>A</span><span style='color:#000000;'>和</span><span style='color:#000000;'>B</span><span style='color:#000000;'>通过一根轻质不可伸长的细绳连接,跨放在质量不计的光滑定滑轮两侧,质量分别为</span><span style='color:#000000;'>mA=2kg</span><span style='color:#000000;'>、</span><span style='color:#000000;'>mB</span><span style='color:#000000;'>=1kg</span><span style='color:#000000;'>。初始时</span><span style='color:#000000;'>A</span><span style='color:#000000;'>静止与水平地面上,</span><span style='color:#000000;'>B</span><span style='color:#000000;'>悬于空中。先将</span><span style='color:#000000;'>B</span><span style='color:#000000;'>竖直向上再举高</span><span style='color:#000000;'>h=1.8m(</span><span style='color:#000000;'>未触及滑轮</span><span style='color:#000000;'>)</span><span style='color:#000000;'>,然后由静止释放。一段时间后细绳绷直,</span><span style='color:#000000;'>A</span><span style='color:#000000;'>、</span><span style='color:#000000;'>B</span><span style='color:#000000;'>以大小相等的速度一起运动,之后</span><span style='color:#000000;'>B</span><span style='color:#000000;'>恰好可以和地面接触</span><span style='color:#000000;'>(</span><span style='color:#000000;'>取</span><span style='color:#000000;'>g=10m/s2)</span><span style='color:#000000;'>。</span></p><p><img src="E:/demo/image_3.png" width="84" height="141" /></p><p><span style='color:#000000;'>(1)B</span><span style='color:#000000;'>从释放到细绳绷直时的运动时间</span><span style='color:#000000;'>t</span><span style='color:#000000;'>;</span></p><p><span style='color:#000000;'>(2)A</span><span style='color:#000000;'>的最大速度</span><span style='color:#000000;'>v</span><span style='color:#000000;'>的大小;</span></p><p><span style='color:#000000;'>(3)</span><span style='color:#000000;'>初始时</span><span style='color:#000000;'>B</span><span style='color:#000000;'>离地面的高度</span><span style='color:#000000;'>H</span><span style='color:#000000;'>。</span></p><p><span style='color:#000000;'>【答案】</span><span style='color:#000000;'>(1)B</span><span style='color:#000000;'>从释放到细绳刚绷直前做自由落体运动,有:</span><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mover accent="true"><mrow><mi>F</mi></mrow><mo>→</mo></mover><mo>=</mo><mi>m</mi><mover accent="true"><mrow><mi>a</mi></mrow><mo>→</mo></mover></math></p><p><span style='color:#000000;'>代入数据解得:</span><span style='color:#000000;'>t=0.6 s</span><span style='color:#000000;'>。</span></p><p><span style='color:#000000;'>(</span><span style='color:#000000;'>2</span><span style='color:#000000;'>)</span><span style='color:#000000;'>根据动量定理得:</span></p><p><span style='color:#000000;'>对</span><span style='color:#000000;'>A(</span><span style='color:#000000;'>取向上为正方向</span><span style='color:#000000;'>)</span><span style='color:#000000;'>,则有:</span><span style='color:#000000;'>F2t-mAgt=</span><span style='color:#000000;'>mAv</span><span style='color:#000000;'>'</span><span style='color:#000000;'>,</span></p><p><span style='color:#000000;'>对</span><span style='color:#000000;'>B(</span><span style='color:#000000;'>取向下为正方向</span><span style='color:#000000;'>)</span><span style='color:#000000;'>,则有:</span><span style='color:#000000;'>-F1t+mBgt=</span><span style='color:#000000;'>mBv</span><span style='color:#000000;'>'-</span><span style='color:#000000;'>mBvB</span><span style='color:#000000;'>;</span><img src="E:/demo/image_4.svg" style="width:9pt;height:14.25pt"/><span style='color:#000000;'> </span></p><p><span style='color:#000000;'>而</span><span style='color:#000000;'>F1=F2</span></p><p><span style='color:#000000;'>且</span><span style='color:#000000;'>vB</span><span style='color:#000000;'>=6m/s</span><span style='color:#000000;'>,</span></p><p><span style='color:#000000;'>得:</span><span style='color:#000000;'>-</span><span style='color:#000000;'>gt</span><span style='color:#000000;'>=2v'+v'-6</span></p><p><span style='color:#000000;'>由于绷直时间极短,因此有:</span><span style='color:#000000;'>v'=2 m/s</span></p><p><span style='color:#000000;'>(3)</span><span style='color:#000000;'>细绳绷直后,</span><span style='color:#000000;'>A</span><span style='color:#000000;'>、</span><span style='color:#000000;'>B</span><span style='color:#000000;'>一起运动,</span><span style='color:#000000;'>B</span><span style='color:#000000;'>恰好可以和地面接触,说明此时</span><span style='color:#000000;'>A</span><span style='color:#000000;'>、</span><span style='color:#000000;'>B</span><span style='color:#000000;'>的速度为零,</span></p><p><span style='color:#000000;'>这一过程中</span><span style='color:#000000;'>A</span><span style='color:#000000;'>、</span><span style='color:#000000;'>B</span><span style='color:#000000;'>组成的系统机械能守恒,有:</span></p><p><span style='color:#000000;'>12(</span><span style='color:#000000;'>mA+</span><span style='color:#000000;'>mB</span><span style='color:#000000;'>)v</span><span style='color:#000000;'>'2+mBgH=</span><span style='color:#000000;'>mAgH</span></p><p><span style='color:#000000;'>代入数据解得:</span><span style='color:#000000;'>H=0.6 m</span><span style='color:#000000;'>。</span></p><p><span style='color:#000000;'>(1)</span><span style='color:#000000;'>运动时间为</span><span style='color:#000000;'>0.6s</span><span style='color:#000000;'>;</span></p><p><span style='color:#000000;'>(2)A</span><span style='color:#000000;'>的最大速度的大小为</span><span style='color:#000000;'>2m/s</span><span style='color:#000000;'>;</span></p><p><span style='color:#000000;'>(3)</span><span style='color:#000000;'>初始时</span><span style='color:#000000;'>B</span><span style='color:#000000;'>离地面的高度为</span><span style='color:#000000;'>0.6m</span><span style='color:#000000;'>。</span></p><table  border=1><tr><td colspan='7'><p><?xml version="1.0" encoding="UTF-16"?><math xmlns="http://www.w3.org/1998/Math/MathML" ><mi>x</mi><mo>=</mo><mfrac><mrow><mo>-</mo><mi>b</mi><mo>±</mo><msqrt><msup><mrow><mi>b</mi></mrow><mrow><mn>2</mn></mrow></msup><mo>-</mo><mn>4</mn><mi>a</mi><mi>c</mi></msqrt></mrow><mrow><mn>2</mn><mi>a</mi></mrow></mfrac></math><span style='color:#000000;'> </span><span style='color:#000000;'>  </span><span style='color:#000000;'>ᏇᏉᏙᏔᏖᙳᙵ</span><span style='color:#000000;'>‡Ω</span><span style='color:#000000;'>