xiatian 5 år sedan
förälder
incheckning
44ae4635e4

+ 156 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/json/JsonImportUtil.java

@@ -0,0 +1,156 @@
+package cn.com.qmth.examcloud.core.questions.base.json;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+
+import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
+import cn.com.qmth.examcloud.core.questions.base.CommonUtils;
+import cn.com.qmth.examcloud.core.questions.base.enums.QuesUnit;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+
+public class JsonImportUtil {
+
+    private static final Logger log = LoggerFactory.getLogger(JsonImportUtil.class);
+
+    private static final String ELEMENT_TYPE_TEXT = "text";
+
+    private static final String ENCODING = "UTF-8";
+
+    private static Configuration CONFIGURATION;
+
+    private static Template QUESTION_HTML;
+    static {
+
+        CONFIGURATION = new Configuration(Configuration.VERSION_2_3_25);
+        // 设置编码
+        CONFIGURATION.setDefaultEncoding(ENCODING);
+        // 设置ftl模板路径
+        CONFIGURATION.setClassForTemplateLoading(JsonImportUtil.class, "/import_template/");
+
+        try {
+            QUESTION_HTML = CONFIGURATION.getTemplate("question_html.ftl", ENCODING);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    public static String questionJson2Html(List<SectionElement> es) {
+        StringWriter result = null;
+        try {
+            result = new StringWriter();
+            Map<String, Object> map = new HashMap<String, Object>();
+            map.put("pList", es);
+            QUESTION_HTML.process(map, result);
+            return result.toString();
+        } catch (Exception e) {
+            throw new ExamCloudRuntimeException(e);
+        }
+    }
+
+    /**
+     * 获取段落的所有文本
+     *
+     * @param p
+     * @return
+     */
+    public static String getPText(List<SectionElement> es) {
+        String returnText = "";
+        for (SectionElement obj : es) {
+            returnText += obj.getValue().trim();
+        }
+        return returnText.trim();
+    }
+
+    /**
+     * 校验段落中是否含有公式或图片
+     *
+     * @param p
+     * @return
+     */
+    public static boolean isText(List<SectionElement> es) {
+        for (SectionElement obj : es) {
+            if (ELEMENT_TYPE_TEXT.equals(obj.getType())) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 过滤试题单元标题
+     *
+     * @param p
+     * @param quesUnit
+     * @return
+     */
+    public static List<SectionElement> formatP(List<SectionElement> p, QuesUnit quesUnit) {
+        for (SectionElement obj : p) {
+            String tmpText = obj.getValue();
+            if (quesUnit == QuesUnit.QUES_BODY) {
+
+                // 过滤题干标题
+                if (tmpText.matches("^\\d{1,}\\.[\\s\\S]*")) {
+                    tmpText = tmpText.replaceFirst("\\d{1,}\\.", "");
+                    obj.setValue(tmpText);
+                    return p;
+                } else if (tmpText.matches("^\\d{1,}$")) {
+                    tmpText = tmpText.replaceFirst("\\d{1,}", "");
+                    obj.setValue(tmpText);
+                } else if (tmpText.contains(".")) {
+                    tmpText = tmpText.replaceFirst("\\.", "");
+                    obj.setValue(tmpText);
+                    return p;
+                }
+
+            } else if (quesUnit == QuesUnit.QUES_OPTION) {
+
+                // 过滤选项标题
+                if (tmpText.matches("^[A-Z]\\.[\\s\\S]*")) {
+                    tmpText = tmpText.replaceFirst("[A-Z]\\.", "");
+                    obj.setValue(tmpText);
+                    return p;
+                } else if (tmpText.matches("^[A-Z]$")) {
+                    tmpText = tmpText.replaceFirst("[A-Z]", "");
+                    obj.setValue(tmpText);
+                } else if (tmpText.contains(".")) {
+                    tmpText = tmpText.replaceFirst("\\.", "");
+                    obj.setValue(tmpText);
+                    return p;
+                }
+
+            } else if (quesUnit == QuesUnit.QUES_ANSWER) {
+                if (tmpText.contains("答案")) {
+                    // 过滤答案标题
+                    tmpText = tmpText.replaceFirst("\\[", "").replaceFirst("\\]", "").replaceFirst("答案", "")
+                            .replaceFirst("[:|:]", "");
+                    obj.setValue(tmpText);
+                }
+            }
+        }
+        return p;
+    }
+
+    public static String getJsonString(List<SectionElement> p) {
+        Gson gson = new Gson();
+        return gson.toJson(p);
+    }
+
+    public static int getOptionNum(String wordText) {
+        if (wordText.matches("^[a-zA-Z]\\.[\\s\\S]*")) {
+            Integer num = CommonUtils.characterToNumber(wordText.substring(0, 1));
+            return num;
+        }
+        return -1;
+    }
+}

+ 35 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/json/SectionElement.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.examcloud.core.questions.base.json;
+
+public class SectionElement {
+
+    private String type;
+
+    private String value;
+
+    private SectionElementParams params;
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public SectionElementParams getParams() {
+        return params;
+    }
+
+    public void setParams(SectionElementParams params) {
+        this.params = params;
+    }
+
+}

+ 25 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/json/SectionElementParams.java

@@ -0,0 +1,25 @@
+package cn.com.qmth.examcloud.core.questions.base.json;
+
+public class SectionElementParams {
+
+    private Integer width;
+
+    private Integer height;
+
+    public Integer getWidth() {
+        return width;
+    }
+
+    public void setWidth(Integer width) {
+        this.width = width;
+    }
+
+    public Integer getHeight() {
+        return height;
+    }
+
+    public void setHeight(Integer height) {
+        this.height = height;
+    }
+
+}

+ 17 - 0
examcloud-core-questions-base/src/main/resources/import_template/question_html.ftl

@@ -0,0 +1,17 @@
+<#if pList?exists>
+	<p>
+		<#list pList as ele>
+			<#if ele.type=="image">
+		        <#if ele.params??>
+					<img src="${ele.value}" height="${ele.params.height}" width="${ele.params.width}"></img>
+				<#else>
+					<img src="${ele.value}"></img>
+				</#if>
+			<#else>
+				<span>
+		            ${ele.value}
+		        </span>
+			</#if>
+		</#list>
+	</p>
+</#if>

+ 16 - 6
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/QuesOption.java

@@ -6,20 +6,22 @@ import java.io.Serializable;
  * Created by songyue on 16/12/27.
  */
 public class QuesOption implements Serializable {
+
     private static final long serialVersionUID = 1L;
 
     /**
-     * 选项编号
-     * 在第一次导入或者新建题目时初始化,
-     * 初始化之后不可改变
+     * 选项编号 在第一次导入或者新建题目时初始化, 初始化之后不可改变
      */
     private String number;
 
-    private String optionBody;//选项内容
+    private String optionBody;// 选项内容
+
+    private String optionBodyWord;// 选项word
+
+    private String optionBodyJson;// 选项json
 
-    private String optionBodyWord;//选项word
     /**
-     * 是否是正确答案  1:是  0:否
+     * 是否是正确答案 1:是 0:否
      */
     private short isCorrect;
 
@@ -58,4 +60,12 @@ public class QuesOption implements Serializable {
         this.isCorrect = isCorrect;
     }
 
+    public String getOptionBodyJson() {
+        return optionBodyJson;
+    }
+
+    public void setOptionBodyJson(String optionBodyJson) {
+        this.optionBodyJson = optionBodyJson;
+    }
+
 }

+ 52 - 30
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/Question.java

@@ -1,14 +1,15 @@
 package cn.com.qmth.examcloud.core.questions.dao.entity;
 
-import cn.com.qmth.examcloud.core.questions.base.question.enums.QuesStructType;
-import cn.com.qmth.examcloud.core.questions.dao.entity.base.IdBase;
-import cn.com.qmth.examcloud.question.commons.core.question.AnswerType;
-import org.springframework.data.annotation.Transient;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
+import org.springframework.data.annotation.Transient;
+
+import cn.com.qmth.examcloud.core.questions.base.question.enums.QuesStructType;
+import cn.com.qmth.examcloud.core.questions.dao.entity.base.IdBase;
+import cn.com.qmth.examcloud.question.commons.core.question.AnswerType;
+
 /**
  * Created by songyue on 16/12/27.
  */
@@ -18,10 +19,12 @@ public class Question extends IdBase {
 
     private String quesBodyWord;// 题干word
 
+    private String quesBodyJson;// 题干Json
+
     /**
      * 试题wordpkg对象序列化数据
      */
-    //private byte[] quesPkg;
+    // private byte[] quesPkg;
 
     /**
      * 试题pkg路径Id
@@ -32,6 +35,8 @@ public class Question extends IdBase {
 
     private String quesAnswerWord;// 答案word
 
+    private String quesAnswerJson;// 答案Json
+
     private String quesAnswerAnalysis;// 答案解析,默认为html
 
     private String quesAnswerAnalysisWord;// 答案word解析
@@ -57,16 +62,17 @@ public class Question extends IdBase {
     private String courseName;// 课程名称
 
     @Deprecated
-    private String courseLevel;//课程层次
+    private String courseLevel;// 课程层次
 
     @Deprecated
-    private String courseMajor;//课程专业
+    private String courseMajor;// 课程专业
 
     private Course course;
 
     private String orgId; // 机构ID
 
     private Map<String, String> quesParams;// 试题属性
+
     /**
      * 是否包含音频
      */
@@ -74,7 +80,7 @@ public class Question extends IdBase {
 
     private List<QuestionAudio> questionAudios;
 
-    private String quesName;//来源大题
+    private String quesName;// 来源大题
 
     private Integer number;
 
@@ -88,15 +94,15 @@ public class Question extends IdBase {
     /**
      * 试题属性
      */
-    private List<QuesProperty> quesProperties;//关联多组属性
+    private List<QuesProperty> quesProperties;// 关联多组属性
 
-    private Double difficultyDegree; //难度系数
+    private Double difficultyDegree; // 难度系数
 
-    private Boolean publicity; //公开度
+    private Boolean publicity; // 公开度
 
     private String difficulty;// 难度
 
-    private List<String> propertyGroup;//试题属性组合(蓝图组卷使用)
+    private List<String> propertyGroup;// 试题属性组合(蓝图组卷使用)
 
     /**
      * 试题标签,用于K12做查询条件
@@ -109,7 +115,7 @@ public class Question extends IdBase {
     private Boolean isolated;
 
     @Transient
-    private List<QuestionAudio> audioList;//音频列表(复制试卷使用,不存数据)
+    private List<QuestionAudio> audioList;// 音频列表(复制试卷使用,不存数据)
 
     public void addAudio(QuestionAudio audio) {
         if (audioList == null) {
@@ -230,22 +236,22 @@ public class Question extends IdBase {
         this.quesParams = quesParams;
     }
 
-//    public byte[] getQuesPkg() {
-//        return quesPkg;
-//    }
-//
-//    public WordprocessingMLPackage getPkgObj() {
-//        if (this.quesPkg != null && this.quesPkg.length > 0) {
-//            return DocxProcessUtil.getPkg(this.quesPkg);
-//        } else {
-//            return null;
-//        }
-//
-//    }
-//
-//    public void setQuesPkg(byte[] quesPkg) {
-//        this.quesPkg = quesPkg;
-//    }
+    // public byte[] getQuesPkg() {
+    // return quesPkg;
+    // }
+    //
+    // public WordprocessingMLPackage getPkgObj() {
+    // if (this.quesPkg != null && this.quesPkg.length > 0) {
+    // return DocxProcessUtil.getPkg(this.quesPkg);
+    // } else {
+    // return null;
+    // }
+    //
+    // }
+    //
+    // public void setQuesPkg(byte[] quesPkg) {
+    // this.quesPkg = quesPkg;
+    // }
 
     public Double getScore() {
         return score;
@@ -415,4 +421,20 @@ public class Question extends IdBase {
         this.answerType = answerType;
     }
 
+    public String getQuesBodyJson() {
+        return quesBodyJson;
+    }
+
+    public void setQuesBodyJson(String quesBodyJson) {
+        this.quesBodyJson = quesBodyJson;
+    }
+
+    public String getQuesAnswerJson() {
+        return quesAnswerJson;
+    }
+
+    public void setQuesAnswerJson(String quesAnswerJson) {
+        this.quesAnswerJson = quesAnswerJson;
+    }
+
 }

+ 1643 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/ImportPaperFromJsonService.java

@@ -0,0 +1,1643 @@
+package cn.com.qmth.examcloud.core.questions.service;
+
+import java.io.File;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.core.questions.base.CommonUtils;
+import cn.com.qmth.examcloud.core.questions.base.IdUtils;
+import cn.com.qmth.examcloud.core.questions.base.converter.utils.FileUtil;
+import cn.com.qmth.examcloud.core.questions.base.enums.PaperStatus;
+import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
+import cn.com.qmth.examcloud.core.questions.base.enums.QuesUnit;
+import cn.com.qmth.examcloud.core.questions.base.exception.PaperException;
+import cn.com.qmth.examcloud.core.questions.base.json.JsonImportUtil;
+import cn.com.qmth.examcloud.core.questions.base.json.SectionElement;
+import cn.com.qmth.examcloud.core.questions.base.question.enums.QuesStructType;
+import cn.com.qmth.examcloud.core.questions.dao.CoursePropertyRepo;
+import cn.com.qmth.examcloud.core.questions.dao.PaperDetailRepo;
+import cn.com.qmth.examcloud.core.questions.dao.PaperDetailUnitRepo;
+import cn.com.qmth.examcloud.core.questions.dao.PaperRepo;
+import cn.com.qmth.examcloud.core.questions.dao.PropertyRepo;
+import cn.com.qmth.examcloud.core.questions.dao.QuesRepo;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Course;
+import cn.com.qmth.examcloud.core.questions.dao.entity.CourseProperty;
+import cn.com.qmth.examcloud.core.questions.dao.entity.ImportPaperCheck;
+import cn.com.qmth.examcloud.core.questions.dao.entity.ImportPaperMsg;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Paper;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetail;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailUnit;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Property;
+import cn.com.qmth.examcloud.core.questions.dao.entity.QuesOption;
+import cn.com.qmth.examcloud.core.questions.dao.entity.QuesProperty;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Question;
+import cn.com.qmth.examcloud.core.questions.service.impl.CourseService;
+
+@Service
+public class ImportPaperFromJsonService {
+
+    protected static final Logger log = LoggerFactory.getLogger(ImportPaperFromJsonService.class);
+
+    @Autowired
+    private PaperRepo paperRepo;
+
+    @Autowired
+    private PaperDetailRepo paperDetailRepo;
+
+    @Autowired
+    private PaperDetailUnitRepo paperDetailUnitRepo;
+
+    @Autowired
+    private QuesRepo quesRepo;
+
+    @Autowired
+    private PaperService paperService;
+
+    @Autowired
+    private CourseService courseService;
+
+    @Autowired
+    private QuesTypeNameService quesTypeNameService;
+
+    @Autowired
+    private CoursePropertyRepo coursePropertyRepo;
+
+    @Autowired
+    private PropertyRepo propertyRepo;
+
+    /**
+     * 保存导入试卷信息
+     *
+     * @param paper
+     * @param paperDetails
+     * @param paperDetailUnits
+     * @param importPaperCheck
+     * @throws PaperException
+     */
+    private Paper savePaper(Paper paper, List<PaperDetail> paperDetails, List<PaperDetailUnit> paperDetailUnits,
+            List<Question> questions, ImportPaperCheck importPaperCheck) throws Exception {
+        Paper tempPaper = null;
+        if (!StringUtils.isEmpty(importPaperCheck.getErrorInfo()))
+            return null;
+        if (paper != null) {
+            // 总分校验
+            checkTotalScore(paper, paperDetailUnits, importPaperCheck);
+            // 大题下小题数量校验
+            checkUnitNum(paperDetailUnits, importPaperCheck);
+            // 校验答案是否不全
+            checkAnswerISfull(paperDetailUnits, importPaperCheck);
+            // 如果合并相同的大题
+            if (paper.getSameName().equals("1")) {
+                paperDetails = mergePaperDetails(paperDetails, paperDetailUnits);
+            }
+            tempPaper = paperRepo.save(paper);
+        }
+        if (paperDetails != null && paperDetails.size() > 0) {
+            paperDetailRepo.saveAll(paperDetails);
+        }
+        if (questions != null && questions.size() > 0) {
+            for (Question question : questions) {
+                String now = CommonUtils.getCurDateTime();
+                question.setCreateTime(now);
+            }
+            quesRepo.saveAll(questions);
+        }
+        if (paperDetailUnits.size() > 0) {
+            paperDetailUnitRepo.saveAll(paperDetailUnits);
+            quesTypeNameService.saveQuesTypeName(paperDetailUnits);
+        }
+        paperService.formatPaper(tempPaper, null);
+        return tempPaper;
+    }
+
+    /**
+     * 合并相同大题
+     *
+     * @param paperDetails
+     * @param paperDetailUnits
+     * @return
+     */
+    private List<PaperDetail> mergePaperDetails(List<PaperDetail> paperDetails,
+            List<PaperDetailUnit> paperDetailUnits) {
+        List<PaperDetail> mergePaperDetails = new ArrayList<>();
+        Set<String> set = new HashSet<>();
+        for (PaperDetail paperDetail : paperDetails) {
+            if (set.add(paperDetail.getName())) {
+                mergePaperDetails.add(paperDetail);
+            }
+        }
+        for (PaperDetail mergePaperDetail : mergePaperDetails) {
+            for (PaperDetailUnit mergePaperDetailUnit : paperDetailUnits) {
+                if (mergePaperDetailUnit.getPaperDetail().getName().equals(mergePaperDetail.getName())) {
+                    mergePaperDetailUnit.setPaperDetail(mergePaperDetail);
+                }
+            }
+        }
+        return mergePaperDetails;
+    }
+
+    /**
+     * 清空试卷对象,加速回收
+     */
+    private void clearPaper(List<PaperDetail> paperDetails, List<PaperDetailUnit> paperDetailUnits,
+            List<Question> questions) {
+        paperDetails.clear();
+        paperDetailUnits.clear();
+        questions.clear();
+    }
+
+    /**
+     * 处理导入试卷
+     *
+     * @param paper
+     * @param user
+     * @param file
+     * @return
+     * @throws Exception
+     */
+
+    public Paper processImportPaper(Paper paper, User user, File file) throws Exception {
+        try {
+            String fileContent = FileUtil.readFileContent(file);
+            if (StringUtils.isEmpty(fileContent)) {
+                throw new PaperException("文件内容为空");
+            }
+            Gson gson = new Gson();
+            List<List<SectionElement>> pList = gson.fromJson(fileContent, new TypeToken<List<List<SectionElement>>>() {
+            }.getType());
+            // 得到前台的课程代码
+            String courseNo = paper.getCourseNo();
+            ImportPaperCheck importPaperCheck = new ImportPaperCheck();
+            // 设置试卷
+            initPaper(paper, paper.getName(), user);
+            // 创建空大题类
+            PaperDetail paperDetail = null;
+            // 创建大题集合
+            List<PaperDetail> paperDetails = new ArrayList<>();
+            // 创建小题集合
+            List<PaperDetailUnit> paperDetailUnits = new ArrayList<>();
+            // 创建试题集合
+            List<Question> questions = new ArrayList<>();
+            // 大题号
+            int mainQuesNum = 0;
+            // 小题号
+            int subQuesNum = 0;
+            // 大题下的小题序号
+            int errorQuesNum = 0;
+            for (int i = 0; i < pList.size(); i++) {
+                List<SectionElement> p = pList.get(i);
+                String pText = JsonImportUtil.getPText(p);
+                if (StringUtils.isEmpty(pText)) {
+                    // 跳过空白段落
+                    continue;
+                } else if (isMainQuesHeader(pText)) {
+                    // 处理大题头信息
+                    processMainQuesHeader(pList, importPaperCheck.getIndex(), importPaperCheck, courseNo);
+                    // 校验大题头信息
+                    if (!checkQuesHeader(importPaperCheck)) {
+                        throw new PaperException(importPaperCheck.getErrorInfo());
+                    }
+                    // 创建大题类
+                    paperDetail = new PaperDetail();
+                    // 设置大题类
+                    initQuesHeader(paper, paperDetail, paperDetails, ++mainQuesNum, importPaperCheck);
+                    // 设置当前索引,防止多余循环
+                    i = importPaperCheck.getIndex() - 1;
+                    errorQuesNum = 0;
+                } else if ((pText.matches("^\\d{1,}\\.[\\s\\S]*") && !pText.startsWith(ImportPaperMsg.left_bracket))
+                        || (isNested(importPaperCheck) && pText.startsWith(ImportPaperMsg.nestedQuestion_start))) {
+                    if (paperDetail == null) {
+                        throw new PaperException("导入文件格式有误,必须有大题头信息,且以 [ 开头!");
+                    }
+                    ++errorQuesNum;
+                    ++subQuesNum;
+                    // 处理试题
+                    // 创建小题类和试题类
+                    PaperDetailUnit paperDetailUnit = new PaperDetailUnit();
+                    Question question = new Question();
+                    // 初始化小题类和试题类
+                    initPaperDetail(paper, paperDetail, paperDetailUnit, question, subQuesNum, importPaperCheck);
+                    // 处理客观题
+                    if (importPaperCheck.getQuesType().equals(ImportPaperMsg.singleSelection)
+                            || importPaperCheck.getQuesType().equals(ImportPaperMsg.multipleSelection)) {
+                        // 处理题干
+                        processQuesBody(pList, importPaperCheck.getIndex(), subQuesNum, question, importPaperCheck,
+                                errorQuesNum);
+                        // 处理选项
+                        processQuesOption(pList, importPaperCheck.getIndex(), subQuesNum, question, importPaperCheck,
+                                errorQuesNum);
+                        // 处理尾信息
+                        processQuesTail(pList, importPaperCheck.getIndex(), subQuesNum, question, paperDetailUnit,
+                                importPaperCheck, false, paper, errorQuesNum);
+                        // 处理选择题的option
+                        processSelectOption(question);
+                    } else if (importPaperCheck.getQuesType().equals(ImportPaperMsg.nestedQuestion_word)) {
+                        // 处理套题
+                        processNestedQues(pList, importPaperCheck.getIndex(), question, paperDetailUnit,
+                                importPaperCheck, paper);
+                    } else {
+                        // 处理其他题型
+                        processQuesBody(pList, importPaperCheck.getIndex(), subQuesNum, question, importPaperCheck,
+                                errorQuesNum);
+                        processQuesTail(pList, importPaperCheck.getIndex(), subQuesNum, question, paperDetailUnit,
+                                importPaperCheck, false, paper, errorQuesNum);
+                        // 填空题空格校验
+                        if (question.getQuestionType().getName().equals(QuesStructType.FILL_BLANK_QUESTION.getName())) {
+                            if (!StringUtils.isBlank(question.getQuesAnswer())) {
+                                processFill(question, paperDetailUnit, importPaperCheck, subQuesNum, errorQuesNum);
+                            }
+                        }
+                    }
+                    question.setQuesPkgPathId(null);
+                    // 设置question与Unit集合数据
+                    question.setCourse(paper.getCourse());
+                    question.setOrgId(user.getRootOrgId().toString());
+                    questions.add(question);
+                    paperDetailUnits.add(paperDetailUnit);
+                    // 设置当前索引,防止多余循环
+                    i = importPaperCheck.getIndex() - 1;
+                } else if (paperDetail == null) {
+                    throw new PaperException("导入文件格式有误,必须有大题头信息,且以[试题分类]开头!");
+                } else {
+                    String errorMsg = pText.length() > 10 ? pText.substring(0, 10) : pText;
+                    if (pText.startsWith(ImportPaperMsg.left_bracket)) {
+                        throw new PaperException(
+                                errorMsg + ",标签格式不正确!正确标签格式:[套题]、[套题数量]、[小题分数]、[答案]、[难度]、[一级属性]、[二级属性]、[公开度]、[小题型]");
+                    }
+                    throw new PaperException(
+                            importPaperCheck.getQuesName() + ":“" + errorMsg + "”" + ImportPaperMsg.errMsg_12);
+                }
+                if (!StringUtils.isEmpty(importPaperCheck.getErrorInfo())) {
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+            }
+            if (paperDetails.size() == 0 || paperDetailUnits.size() == 0) {
+                throw new PaperException("导入文件格式有误!");
+            }
+            paper.setPaperDetailCount(mainQuesNum);
+            // 保存导入试卷信息
+            paper = savePaper(paper, paperDetails, paperDetailUnits, questions, importPaperCheck);
+            clearPaper(paperDetails, paperDetailUnits, questions);
+        } finally {
+            FileUtils.deleteQuietly(file);
+        }
+        return paper;
+    }
+
+    private boolean isMainQuesHeader(String pText) {
+        /*
+         * return pText.startsWith(ImportPaperMsg.left_bracket) &&
+         * !pText.startsWith(ImportPaperMsg.answer_word) &&
+         * !pText.startsWith(ImportPaperMsg.score_word) &&
+         * !pText.startsWith(ImportPaperMsg.subQuestionType_word) &&
+         * !pText.startsWith(ImportPaperMsg.nestedQuestion_start) &&
+         * !pText.startsWith(ImportPaperMsg.nestedQuestion_number);
+         */
+        return pText.startsWith(ImportPaperMsg.questionClassify_word)
+                || pText.startsWith(ImportPaperMsg.questionType_word)
+                || pText.startsWith(ImportPaperMsg.questionName_word)
+                || pText.startsWith(ImportPaperMsg.questionNum_word) || pText.startsWith(ImportPaperMsg.score_word);
+    }
+
+    private boolean isHeader(String pText) {
+        return pText.startsWith(ImportPaperMsg.questionClassify_word)
+                || pText.startsWith(ImportPaperMsg.questionType_word)
+                || pText.startsWith(ImportPaperMsg.questionName_word)
+                || pText.startsWith(ImportPaperMsg.questionNum_word) || pText.startsWith(ImportPaperMsg.score_word)
+                || pText.startsWith(ImportPaperMsg.answer_word) || pText.startsWith(ImportPaperMsg.subQuestionType_word)
+                || pText.startsWith(ImportPaperMsg.subQuesScore_word)
+                || pText.startsWith(ImportPaperMsg.nestedQuestion_start)
+                || pText.startsWith(ImportPaperMsg.nestedQuestion_number)
+                || pText.startsWith(ImportPaperMsg.left_bracket);
+    }
+
+    /**
+     * 初始化试卷信息
+     *
+     * @param paper
+     * @param paperName
+     * @param user
+     */
+    private void initPaper(Paper paper, String paperName, User user) {
+        paper.setName(paperName);
+        paper.setTitle(paperName);
+        paper.setPaperType(PaperType.IMPORT);
+        paper.setPaperStatus(PaperStatus.DRAFT);
+        paper.setOrgId(user.getRootOrgId().toString());
+        paper.setCreator(user.getDisplayName());
+        Course course = courseService.getCourse(user.getRootOrgId(), paper.getCourseNo());
+        paper.setCourse(course);
+        paper.setCourseName(course.getName());
+        paper.setCreateTime(CommonUtils.getCurDateTime());
+    }
+
+    /**
+     * 初始化大题头信息
+     *
+     * @param paper
+     * @param paperDetail
+     * @param paperDetails
+     * @param mainQuesNum
+     * @param importPaperCheck
+     */
+    private void initQuesHeader(Paper paper, PaperDetail paperDetail, List<PaperDetail> paperDetails, int mainQuesNum,
+            ImportPaperCheck importPaperCheck) {
+
+        paperDetail.setPaper(paper);
+        // 设置大题信息
+        paperDetail.setName(importPaperCheck.getQuesName());
+        paperDetail.setUnitCount(Integer.parseInt(importPaperCheck.getQuesCount()));
+        paperDetail.setNumber(mainQuesNum);
+        paperDetail.setCreator(paper.getCreator());
+        paperDetails.add(paperDetail);
+
+    }
+
+    /**
+     * 初始化试卷明细和试题
+     *
+     * @param paperDetail
+     * @param paperDetailUnit
+     * @param question
+     * @param subQuesNum
+     * @param importPaperCheck
+     */
+    private void initPaperDetail(Paper paper, PaperDetail paperDetail, PaperDetailUnit paperDetailUnit,
+            Question question, int subQuesNum, ImportPaperCheck importPaperCheck) {
+
+        // 重置importPaperCheck
+        importPaperCheck.setNestedHeadNumber(0);
+        question.setQuestionType(getQuesStructType(importPaperCheck.getQuesType()));
+        question.setScore(Double.parseDouble(importPaperCheck.getQuesScore()));
+        paperDetailUnit.setNumber(subQuesNum);
+        paperDetailUnit.setQuestion(question);
+        paperDetailUnit.setPaperDetail(paperDetail);
+        paperDetailUnit.setQuestionType(getQuesStructType(importPaperCheck.getQuesType()));
+        paperDetailUnit.setScore(Double.parseDouble(importPaperCheck.getQuesScore()));
+        paperDetailUnit.setCreator(paper.getCreator());
+        paperDetailUnit.setPaper(paper);
+        paperDetailUnit.setPaperType(PaperType.IMPORT);
+    }
+
+    /**
+     * 校验试题头标识
+     *
+     * @param importPaperCheck
+     * @return
+     */
+    private boolean checkQuesHeader(ImportPaperCheck importPaperCheck) {
+
+        String tmpErrorInfo = "";
+        if (StringUtils.isEmpty(importPaperCheck.getQuesGroup())) {
+            tmpErrorInfo += ImportPaperMsg.errMsg_04;
+
+        }
+        if (StringUtils.isEmpty(importPaperCheck.getQuesType())) {
+            tmpErrorInfo += ImportPaperMsg.errMsg_05;
+
+        }
+        if (StringUtils.isEmpty(importPaperCheck.getQuesName())) {
+            tmpErrorInfo += ImportPaperMsg.errMsg_06;
+
+        }
+        if (StringUtils.isEmpty(importPaperCheck.getQuesCount())) {
+            tmpErrorInfo += ImportPaperMsg.errMsg_07;
+
+        }
+        if (StringUtils.isEmpty(importPaperCheck.getQuesScore())) {
+            tmpErrorInfo += ImportPaperMsg.errMsg_08;
+        }
+        if (!StringUtils.isEmpty(importPaperCheck.getQuesType())
+                && !CommonUtils.checkQuesType(importPaperCheck.getQuesType())) {
+            tmpErrorInfo += ImportPaperMsg.errMsg_09;
+        }
+        if (StringUtils.isEmpty(tmpErrorInfo)) {
+            return true;
+        } else {
+            importPaperCheck.setErrorInfo(tmpErrorInfo);
+            return false;
+        }
+    }
+
+    /**
+     * 处理大题头信息
+     *
+     * @param pList
+     * @param index
+     * @param importPaperCheck
+     * @throws PaperException
+     */
+    private void processMainQuesHeader(List<List<SectionElement>> pList, int index, ImportPaperCheck importPaperCheck,
+            String courseNo) throws PaperException {
+        int i = 0;
+        for (i = index; i < pList.size(); i++) {
+            List<SectionElement> pFirst = pList.get(i);
+            String tmpTextFirst = JsonImportUtil.getPText(pFirst);
+            if (StringUtils.isBlank(tmpTextFirst)) {
+                continue;
+            }
+            if (!tmpTextFirst.startsWith(ImportPaperMsg.questionClassify_word)) {
+                throw new PaperException("导入文件格式有误,必须有大题头信息,且以[试题分类]开头!");
+            }
+            break;
+        }
+        for (i = index; i < pList.size(); i++) {
+            List<SectionElement> pHeader = pList.get(i);
+            List<SectionElement> pNextHeader = pList.get(i + 1);
+            String tmpText = JsonImportUtil.getPText(pHeader);
+            String nextTmpText = JsonImportUtil.getPText(pNextHeader);
+            if (tmpText.startsWith(ImportPaperMsg.questionClassify_word)) {
+                if (!nextTmpText.startsWith(ImportPaperMsg.questionType_word)) {
+                    importPaperCheck.setErrorInfo("[试题分类]的下一题头应该是[题型],当前读到的信息为:“" + nextTmpText + "”,请检查word中格式问题");
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                List<String> paperList = Stream.of(tmpText.split("_")).collect(Collectors.toList());
+                // 获取[试题分类]中的课程代码
+                if (paperList.size() != 2) {
+                    importPaperCheck.setErrorInfo("[试题分类]的格式不正确或者是内容为空");
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                String impCourseCode = paperList.get(1);
+                if (!courseNo.equals(impCourseCode)) {
+                    importPaperCheck.setErrorInfo("[试题分类]中的课程代码与指定的课程代码不一样,当前读到的信息为:“" + tmpText + "”,请检查word中格式问题");
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                importPaperCheck.setQuesGroup(getContent(tmpText, ImportPaperMsg.questionClassify_word));
+            } else if (tmpText.startsWith(ImportPaperMsg.questionType_word)) {
+                if (!nextTmpText.startsWith(ImportPaperMsg.questionName_word)) {
+                    importPaperCheck.setErrorInfo("[题型]的下一题头应该是[大题名称],当前读到的信息为:“" + nextTmpText + "”,请检查word中格式问题");
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                importPaperCheck.setQuesType(getContent(tmpText, ImportPaperMsg.questionType_word));
+            } else if (tmpText.startsWith(ImportPaperMsg.questionName_word)) {
+                if (!nextTmpText.startsWith(ImportPaperMsg.questionNum_word)) {
+                    importPaperCheck.setErrorInfo("[大题名称]的下一题头应该是[题目数量],当前读到的信息为:“" + nextTmpText + "”,请检查word中格式问题");
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                importPaperCheck.setQuesName(getContent(tmpText, ImportPaperMsg.questionName_word));
+            } else if (tmpText.startsWith(ImportPaperMsg.questionNum_word)) {
+                if (!CommonUtils.isInteger(getContent(tmpText, ImportPaperMsg.questionNum_word))) {
+                    importPaperCheck.setErrorInfo("[题目数量]的格式不正确");
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                if (!nextTmpText.startsWith(ImportPaperMsg.score_word)) {
+                    importPaperCheck.setErrorInfo("[题目数量]的下一题头应该是[分数],当前读到的信息为:“" + nextTmpText + "”,请检查word中格式问题");
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                importPaperCheck.setQuesCount(getContent(tmpText, ImportPaperMsg.questionNum_word));
+            } else if (tmpText.startsWith(ImportPaperMsg.score_word)) {
+                if (!CommonUtils.isInteger(getContent(tmpText, ImportPaperMsg.score_word))) {
+                    importPaperCheck.setErrorInfo("[分数]的格式不正确");
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                importPaperCheck.setQuesScore(getContent(tmpText, ImportPaperMsg.score_word));
+            } else if (StringUtils.isEmpty(tmpText)) {
+                continue;
+            } else {
+                break;
+            }
+        }
+        importPaperCheck.setIndex(i);
+    }
+
+    /**
+     * 处理套题的头信息
+     *
+     * @param pList
+     * @param index
+     * @param subQuesNum
+     * @param question
+     * @param importPaperCheck
+     * @throws Exception
+     */
+    private void processNestedQuestionHead(List<List<SectionElement>> pList, int index, int subQuesNum,
+            Question question, ImportPaperCheck importPaperCheck, PaperDetailUnit paperDetailUnit) throws Exception {
+        int i = 0;
+        for (i = index; i < pList.size(); i++) {
+            List<SectionElement> pHeader = pList.get(i);
+            List<SectionElement> pNextHeader = pList.get(i + 1);
+            String tmpText = JsonImportUtil.getPText(pHeader);
+            String nextTmpText = JsonImportUtil.getPText(pNextHeader);
+            if (StringUtils.isEmpty(tmpText)) {
+                // 跳过空白段落
+                continue;
+            } else if (tmpText.startsWith(ImportPaperMsg.nestedQuestion_start)) {
+                // 判断【套题】后面的值
+                if (StringUtils.isBlank(getContent(tmpText, ImportPaperMsg.nestedQuestion_start))) {
+                    importPaperCheck.setErrorInfo(paperDetailUnit.getPaperDetail().getName()
+                            + "中“[套题]:”的后面没有值,当前读到的信息为:“" + tmpText + "”,请检查word中格式问题");
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                Pattern pattern = Pattern.compile("[0-9]*");
+                boolean isHeadNumber = pattern.matcher(getContent(tmpText, ImportPaperMsg.nestedQuestion_start))
+                        .matches();
+                if (!isHeadNumber) {
+                    importPaperCheck.setErrorInfo("[套题]后面应该为整数,当前读到的信息为:“" + tmpText + "”,请检查word中格式问题");
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                // 判断是否含有【套题数量】标签
+                if (!nextTmpText.startsWith(ImportPaperMsg.nestedQuestion_number)) {
+                    // 如果读取的错误信息太长,截取前50个字符。
+                    if (nextTmpText.length() > 50) {
+                        nextTmpText = nextTmpText.substring(0, 50) + "...";
+                    }
+                    importPaperCheck.setErrorInfo("[大题名称]:" + paperDetailUnit.getPaperDetail().getName() + "," + tmpText
+                            + "的下一题头应该是[套题数量],当前读到的信息为:“" + nextTmpText + "”,请检查word中格式问题");
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                // 判断【套题数量】后面的值
+                if (StringUtils.isBlank(getContent(nextTmpText, ImportPaperMsg.nestedQuestion_number))) {
+                    importPaperCheck.setErrorInfo("[套题数量]的后面没有值,当前读到的信息为:“" + nextTmpText + "”,请检查word中格式问题");
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                boolean isNumber = pattern.matcher(getContent(nextTmpText, ImportPaperMsg.nestedQuestion_number))
+                        .matches();
+
+                if (!isNumber) {
+                    importPaperCheck.setErrorInfo("[套题数量]应该为整数,当前读到的信息为:“" + nextTmpText + "”,请检查word中格式问题");
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                importPaperCheck.setNestedHeadNumber(
+                        Integer.parseInt(getContent(tmpText, ImportPaperMsg.nestedQuestion_start)));
+                importPaperCheck.setNestedQuesNumber(
+                        Integer.parseInt(getContent(nextTmpText, ImportPaperMsg.nestedQuestion_number)));
+            } else {
+                importPaperCheck.setIndex(i + 1);
+                break;
+            }
+        }
+    }
+
+    /**
+     * 处理题干信息
+     *
+     * @param pList
+     * @param index
+     * @param question
+     * @param importPaperCheck
+     * @throws Exception
+     */
+    private void processQuesBody(List<List<SectionElement>> pList, int index, int subQuesNum, Question question,
+            ImportPaperCheck importPaperCheck, int errorQuesNum) throws Exception {
+        // 定义题干json和html
+        StringBuilder quesBodyJson = new StringBuilder();
+        StringBuilder quesBodyHtml = new StringBuilder();
+        int i = 0;
+        for (i = index; i < pList.size(); i++) {
+            List<SectionElement> pBody = pList.get(i);
+            String tmpText = JsonImportUtil.getPText(pBody);
+            if (StringUtils.isEmpty(tmpText) && JsonImportUtil.isText(pBody)) {
+                // 跳过空白段落
+                continue;
+            } else if (tmpText.matches("^\\d{1,}\\.[\\s\\S]*")) {
+                // 题干第一段
+                String tmpJson = JsonImportUtil.getJsonString(pBody);
+                quesBodyJson.append(tmpJson);
+                // 过滤题干标题
+                pBody = JsonImportUtil.formatP(pBody, QuesUnit.QUES_BODY);
+                quesBodyHtml.append(JsonImportUtil.questionJson2Html(pBody));
+            } else if (tmpText.matches("^[a-zA-Z]\\.[\\s\\S]*") || isHeader(tmpText)) {
+                // 检测到选项或其他特殊段落直接退出
+                break;
+            } else {
+                // 题干普通段落
+                String tmpJson = JsonImportUtil.getJsonString(pBody);
+                quesBodyJson.append(tmpJson);
+                quesBodyHtml.append(JsonImportUtil.questionJson2Html(pBody));
+            }
+        }
+        if (StringUtils.isEmpty(quesBodyHtml) || StringUtils.isEmpty(quesBodyJson)) {
+            if (subQuesNum == 0) {
+                if (importPaperCheck.getNestedHeadNumber() != 0) {
+                    importPaperCheck.setErrorInfo(importPaperCheck.getQuesName() + "第"
+                            + importPaperCheck.getNestedHeadNumber() + "个题" + ImportPaperMsg.errMsg_01);
+                } else {
+                    importPaperCheck.setErrorInfo(
+                            getQuesNumInfo(importPaperCheck.getQuesName(), errorQuesNum) + ImportPaperMsg.errMsg_01);
+                }
+            } else {
+                if (importPaperCheck.getNestedHeadNumber() != 0) {
+                    importPaperCheck.setErrorInfo(importPaperCheck.getQuesName() + "第"
+                            + importPaperCheck.getNestedHeadNumber() + "个题" + errorQuesNum + ImportPaperMsg.errMsg_01);
+                } else {
+                    importPaperCheck.setErrorInfo(
+                            getQuesNumInfo(importPaperCheck.getQuesName(), errorQuesNum) + ImportPaperMsg.errMsg_01);
+                }
+            }
+            throw new PaperException(importPaperCheck.getErrorInfo());
+        }
+        importPaperCheck.setIndex(i);
+        question.setQuesBody(quesBodyHtml.toString());
+        question.setQuesBodyJson(quesBodyJson.toString());
+    }
+
+    /**
+     * 处理题目选项
+     *
+     * @param pList
+     * @param index
+     * @param subQuesNum
+     * @param question
+     * @param importPaperCheck
+     * @param wordMLPackage
+     * @return
+     * @throws Exception
+     */
+    private String processQuesOption(List<List<SectionElement>> pList, int index, int subQuesNum, Question question,
+            ImportPaperCheck importPaperCheck, int errorQuesNum) throws Exception {
+
+        // 定义选项集合
+        List<QuesOption> quesOptions = new ArrayList<>();
+        // 定义选项数量
+        int optionCount = 0;
+        // 当前所处的选项
+        QuesOption current = null;
+        int i = 0;
+        int number = 0;
+        for (i = index; i < pList.size(); i++) {
+            List<SectionElement> pOption = pList.get(i);
+            String tmpText = JsonImportUtil.getPText(pOption);
+            if (StringUtils.isEmpty(tmpText) && JsonImportUtil.isText(pOption)) {
+                // 跳过空白段落
+                continue;
+            } else if (tmpText.matches("^[a-zA-Z]\\.[\\s\\S]*")) {
+                // 检测到选项开始段落
+                // 创建试题选项
+                number++;
+                current = new QuesOption();
+                int optionNumber = JsonImportUtil.getOptionNum(tmpText);
+                if (optionNumber != number || "abcdefghijklmnopqrstuvwxyz".indexOf(tmpText.substring(0, 1)) > -1) {
+                    String errorMsg = tmpText.length() > 10 ? tmpText.substring(0, 10) : tmpText;
+                    if (importPaperCheck.getNestedHeadNumber() != 0) {
+                        importPaperCheck.setErrorInfo(
+                                importPaperCheck.getQuesName() + "第" + importPaperCheck.getNestedHeadNumber() + "个题"
+                                        + errorQuesNum + ":“" + errorMsg + "”" + ImportPaperMsg.errMsg_13);
+                    } else {
+                        importPaperCheck.setErrorInfo(getQuesNumInfo(importPaperCheck.getQuesName(), errorQuesNum)
+                                + ":“" + errorMsg + "”" + ImportPaperMsg.errMsg_13);
+                    }
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                current.setNumber(String.valueOf(optionNumber));
+                current.setOptionBody("");
+                current.setOptionBodyJson("");
+                quesOptions.add(current);
+                optionCount++;
+                String tmpJson = JsonImportUtil.getJsonString(pOption);
+                current.setOptionBodyJson(current.getOptionBodyJson() + tmpJson);
+                // 过滤选项标题
+                pOption = JsonImportUtil.formatP(pOption, QuesUnit.QUES_OPTION);
+                current.setOptionBody(current.getOptionBody() + JsonImportUtil.questionJson2Html(pOption));
+            } else if (isHeader(tmpText)) {
+                // 非选项的其他内容直接退出
+                break;
+            } else if (current != null) {
+                // 选项的其他段落
+                String tmpJson = JsonImportUtil.getJsonString(pOption);
+                current.setOptionBody(current.getOptionBody() + JsonImportUtil.questionJson2Html(pOption));
+                current.setOptionBodyJson(current.getOptionBodyJson() + tmpJson);
+            } else {
+                break;
+            }
+        }
+        importPaperCheck.setIndex(i);
+        if (optionCount < 2) {
+            if (importPaperCheck.getNestedHeadNumber() != 0) {
+                importPaperCheck.setErrorInfo(importPaperCheck.getQuesName() + "第"
+                        + importPaperCheck.getNestedHeadNumber() + "个题" + errorQuesNum + "中选项格式不正确或有缺失\n");
+            } else {
+                importPaperCheck
+                        .setErrorInfo(getQuesNumInfo(importPaperCheck.getQuesName(), errorQuesNum) + "中选项格式不正确或有缺失\n");
+            }
+            throw new PaperException(importPaperCheck.getErrorInfo());
+        } else {
+            question.setQuesOptions(quesOptions);
+        }
+        return importPaperCheck.getErrorInfo();
+    }
+
+    /**
+     * 处理小题尾信息
+     *
+     * @param pList
+     * @param index
+     * @param subQuesNum
+     * @param question
+     * @param paperDetailUnit
+     * @param importPaperCheck
+     */
+    private void processQuesTail(List<List<SectionElement>> pList, int index, int subQuesNum, Question question,
+            PaperDetailUnit paperDetailUnit, ImportPaperCheck importPaperCheck, boolean isNested, Paper paper,
+            int errorQuesNum) throws Exception {
+
+        StringBuilder answerJson = new StringBuilder("");
+        StringBuilder answerHTML = new StringBuilder("");
+        String quesScore = null;
+        // 定义一级属性
+        String firstProperty = null;
+        // 定义二级级属性
+        String secondProperty = null;
+        // 定义难度
+        Double difficulty = null;
+        // 定义公开度
+        Boolean publicity = null;
+        // 定义试题属性集合
+        List<QuesProperty> quesProperties = new ArrayList<>();
+        // Map<String, String> quesParams = new HashMap<>();
+        int i = 0;
+        // 是否刚刚检测到答案内容
+        boolean answerStart = false;
+        boolean hasAnswer = false;
+
+        for (i = index; i < pList.size(); i++) {
+            List<SectionElement> pAnswer = pList.get(i);
+            String tmpText = JsonImportUtil.getPText(pAnswer);
+            String tmpJson = JsonImportUtil.getJsonString(pAnswer);
+            if (StringUtils.isEmpty(tmpText) && JsonImportUtil.isText(pAnswer)) {
+                // 跳过空白段落
+                continue;
+            } else if (tmpText.startsWith(ImportPaperMsg.answer_word)) {
+                // 检测到答案开始段落
+                pAnswer = JsonImportUtil.formatP(pAnswer, QuesUnit.QUES_ANSWER);
+                if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+                        || question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+                    // 校验单选多选答案
+                    String answerText = getContent(tmpText, ImportPaperMsg.answer_word);
+                    checkSelectAnswer(question, answerText, importPaperCheck, errorQuesNum);
+                    answerJson.append(tmpJson);
+                    answerHTML.append(getContent(tmpText, ImportPaperMsg.answer_word));
+                } else if (question.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION) {
+                    // 校验判断题答案
+                    String answerText = getContent(tmpText, ImportPaperMsg.answer_word);
+                    checkBoolAnswer(answerText, importPaperCheck, errorQuesNum);
+                    answerJson.append(tmpJson);
+                    answerHTML.append(answerText);
+                } else {
+                    answerJson.append(tmpJson);
+                    answerHTML.append(JsonImportUtil.questionJson2Html(pAnswer));
+                }
+                hasAnswer = true;
+                answerStart = true;
+            } else if (tmpText.startsWith(ImportPaperMsg.subQuesScore_word)) {
+                // 检测到分数开始段落
+                quesScore = getContent(tmpText, ImportPaperMsg.subQuesScore_word);
+                if (!CommonUtils.isInteger(quesScore)) {
+                    if (importPaperCheck.getNestedHeadNumber() == 0) {
+                        importPaperCheck.setErrorInfo(
+                                "[大题名称]:" + importPaperCheck.getQuesName() + "第" + errorQuesNum + "题中[小题分数]格式不对或没有值");
+                    } else {
+                        importPaperCheck.setErrorInfo("[大题名称]:" + importPaperCheck.getQuesName() + "第"
+                                + importPaperCheck.getNestedHeadNumber() + "个套题中,第" + errorQuesNum
+                                + "题中[小题分数]格式不对或没有值");
+                    }
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                answerStart = false;
+            } else if (tmpText.startsWith(ImportPaperMsg.first_property)) {
+                // 检测到一级属性开始段落
+                firstProperty = getContent(tmpText, ImportPaperMsg.first_property);
+                if (StringUtils.isBlank(firstProperty)) {
+                    firstProperty = "";
+                }
+                answerStart = false;
+            } else if (tmpText.startsWith(ImportPaperMsg.second_property)) {
+                // 检测到二级属性开始段落
+                secondProperty = getContent(tmpText, ImportPaperMsg.second_property);
+                if (StringUtils.isBlank(secondProperty)) {
+                    secondProperty = "";
+                }
+                answerStart = false;
+            } else if (tmpText.startsWith(ImportPaperMsg.difficulty)) {
+                // 检测到难度开始段落 difficultyDegree
+                String dif = getContent(tmpText, ImportPaperMsg.difficulty);
+
+                difficulty = checkDifficulty(dif, importPaperCheck, errorQuesNum, question);
+                question.setDifficultyDegree(difficulty);
+                answerStart = false;
+            } else if (tmpText.startsWith(ImportPaperMsg.publicity)) {
+                // 检测到公安度开始段落
+                String pub = getContent(tmpText, ImportPaperMsg.publicity);
+
+                publicity = checkPublicity(pub, importPaperCheck, errorQuesNum);
+                question.setPublicity(publicity);
+                answerStart = false;
+            } else if (isHeader(tmpText) || tmpText.matches("^\\d{1,}\\.[\\s\\S]*")) {
+                // 检测到其他特殊段落或下一题直接退出
+                break;
+            } else if (answerStart) {
+                answerJson.append(tmpJson);
+                answerHTML.append(JsonImportUtil.questionJson2Html(pAnswer));
+            } else {
+                break;
+            }
+        }
+        importPaperCheck.setIndex(i);
+        // 如果没有答案字段,抛出异常
+        if (!hasAnswer) {
+            if (paperDetailUnit.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+                importPaperCheck.setErrorInfo(paperDetailUnit.getPaperDetail().getName() + "中,第"
+                        + paperDetailUnit.getNumber() + "个套题的" + errorQuesNum + "小题中,缺失“[答案]”");
+            } else {
+                importPaperCheck
+                        .setErrorInfo(getQuesNumInfo(importPaperCheck.getQuesName(), errorQuesNum) + "中,缺失“[答案]”");
+            }
+            throw new PaperException(importPaperCheck.getErrorInfo());
+        }
+        // 设置答案
+        if (StringUtils.isNotEmpty(answerHTML)) {
+            question.setQuesAnswer(answerHTML.toString());
+            question.setQuesAnswerJson(answerJson.toString());
+        } else {
+            question.setQuesAnswer("<p></p>");
+            question.setQuesAnswerJson(answerJson.toString());
+        }
+
+        if (quesScore != null) {
+            question.setScore(Double.parseDouble(quesScore));
+        }
+        // 一般大题明细需要设置分数
+        if (!isNested && quesScore != null) {
+            paperDetailUnit.setScore(Double.parseDouble(quesScore));
+        }
+        // 校验小题尾信息是否含有"一节属性","二级属性","难度","公开"
+        checkAttributeIsFull(firstProperty, secondProperty, difficulty, publicity, importPaperCheck, errorQuesNum);
+        // 试题一级属性与二级属性校验
+        checkProperty(firstProperty, secondProperty, importPaperCheck, errorQuesNum, paper, quesProperties);
+        question.setQuesProperties(quesProperties);
+    }
+
+    /**
+     * 校验一级属性与二级属性,并保存到试题中
+     *
+     * @param firstProperty
+     * @param secondProperty
+     * @param importPaperCheck
+     * @param subQuesNum
+     * @param paper
+     * @param quesProperties
+     * @throws Exception
+     */
+    private void checkProperty(String firstProperty, String secondProperty, ImportPaperCheck importPaperCheck,
+            int subQuesNum, Paper paper, List<QuesProperty> quesProperties) throws Exception {
+        // 一级属性为空,二级属性有值
+        if (StringUtils.isBlank(firstProperty) && StringUtils.isNotBlank(secondProperty)) {
+            if (importPaperCheck.getNestedHeadNumber() != 0) {
+                importPaperCheck.setErrorInfo(importPaperCheck.getQuesName() + "第"
+                        + importPaperCheck.getNestedHeadNumber() + "个题" + subQuesNum + "中,小题一级属性值为空,请检查");
+            } else {
+                importPaperCheck
+                        .setErrorInfo(getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,小题一级属性值为空,请检查");
+            }
+            throw new PaperException(importPaperCheck.getErrorInfo());
+        }
+        // 一级属性,二级属性都有值
+        else if (StringUtils.isNotBlank(firstProperty) && StringUtils.isNotBlank(secondProperty)) {
+            boolean isFirstEmpty = true;
+            boolean isSecondEmpty = true;
+            // 根据课程查询所有课程属性树
+            List<CourseProperty> courseProperties = coursePropertyRepo
+                    .findByCourseCodeAndEnable(paper.getCourse().getCode(), true);
+            if (courseProperties == null || courseProperties.size() < 1) {
+                if (importPaperCheck.getNestedHeadNumber() != 0) {
+                    importPaperCheck.setErrorInfo(importPaperCheck.getQuesName() + "第"
+                            + importPaperCheck.getNestedHeadNumber() + "个题" + subQuesNum + "中,没有设置课程属性结构,请先设置课程属性结构");
+                } else {
+                    importPaperCheck.setErrorInfo(
+                            getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,没有设置课程属性结构,请先设置课程属性结构");
+                }
+                throw new PaperException(importPaperCheck.getErrorInfo());
+            }
+
+            for (CourseProperty courseProperty : courseProperties) {
+                Property propertyParent = new Property();
+                propertyParent.setCoursePropertyId(courseProperty.getId());
+                propertyParent.setParentId(Property.ROOT_PARENT_ID);
+                propertyParent.setName(firstProperty);
+
+                // 查询一级属性是否存在
+                List<Property> parentProperties = propertyRepo.findAll(Example.of(propertyParent));
+
+                // 存在一级属性
+                if (parentProperties != null && parentProperties.size() > 0) {
+                    isFirstEmpty = false;
+                    for (Property proParent : parentProperties) {
+                        Property propertySon = new Property();
+                        propertySon.setCoursePropertyId(courseProperty.getId());
+                        propertySon.setParentId(proParent.getId());
+                        propertySon.setName(secondProperty);
+
+                        // 查询二级属性
+                        List<Property> sonProperties = propertyRepo.findAll(Example.of(propertySon));
+
+                        // 存在二级属性
+                        if (sonProperties != null && sonProperties.size() > 0) {
+                            isSecondEmpty = false;
+                            for (Property proSon : sonProperties) {
+                                // 保存一级和二级属性
+                                QuesProperty quesProperty = new QuesProperty(proParent, proSon, courseProperty);
+                                String idNumber = quesProperty.getCoursePropertyName() + "-"
+                                        + quesProperty.getFirstProperty().getId() + "-"
+                                        + quesProperty.getSecondProperty().getId();
+                                quesProperty.setId(idNumber);
+                                quesProperties.add(quesProperty);
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (isFirstEmpty) {
+                if (importPaperCheck.getNestedHeadNumber() != 0) {
+                    importPaperCheck
+                            .setErrorInfo(importPaperCheck.getQuesName() + "第" + importPaperCheck.getNestedHeadNumber()
+                                    + "个题" + subQuesNum + "中,小题一级属性值与课程属性中一级属性不匹配,请检查");
+                } else {
+                    importPaperCheck.setErrorInfo(
+                            getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,小题一级属性值与课程属性中一级属性不匹配,请检查");
+                }
+                throw new PaperException(importPaperCheck.getErrorInfo());
+            }
+
+            if (isSecondEmpty) {
+                if (importPaperCheck.getNestedHeadNumber() != 0) {
+                    importPaperCheck
+                            .setErrorInfo(importPaperCheck.getQuesName() + "第" + importPaperCheck.getNestedHeadNumber()
+                                    + "个题" + subQuesNum + "中,小题二级属性值与课程属性中二级属性不匹配,请检查");
+                } else {
+                    importPaperCheck.setErrorInfo(
+                            getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,小题二级属性值与课程属性中二级属性不匹配,请检查");
+                }
+                throw new PaperException(importPaperCheck.getErrorInfo());
+            }
+        } else if (StringUtils.isNotBlank(firstProperty) && StringUtils.isBlank(secondProperty)) {
+            // 一级属性有值,二级属性为空
+
+            boolean isFirstEmpty = true;
+            boolean isSecondEmpty = true;
+
+            // 根据课程查询所有课程属性树
+            List<CourseProperty> courseProperties = coursePropertyRepo
+                    .findByCourseCodeAndEnable(paper.getCourse().getCode(), true);
+            if (courseProperties == null || courseProperties.size() < 1) {
+                if (importPaperCheck.getNestedHeadNumber() != 0) {
+                    importPaperCheck.setErrorInfo(importPaperCheck.getQuesName() + "第"
+                            + importPaperCheck.getNestedHeadNumber() + "个题" + subQuesNum + "中,没有设置课程属性结构,请先设置课程属性结构");
+                } else {
+                    importPaperCheck.setErrorInfo(
+                            getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,没有设置课程属性结构,请先设置课程属性结构");
+                }
+                throw new PaperException(importPaperCheck.getErrorInfo());
+            }
+
+            for (CourseProperty courseProperty : courseProperties) {
+                // 查询一级属性
+                Property propertyParent = new Property();
+                propertyParent.setCoursePropertyId(courseProperty.getId());
+                propertyParent.setParentId(Property.ROOT_PARENT_ID);
+                propertyParent.setName(firstProperty);
+
+                List<Property> parentProperties = propertyRepo.findAll(Example.of(propertyParent));
+
+                // 存在一级属性
+                if (parentProperties != null && parentProperties.size() > 0) {
+                    isFirstEmpty = false;
+                    for (Property proParent : parentProperties) {
+                        // 根据一级属性查询二级属性
+                        List<Property> sonProperties = propertyRepo.findByParentIdOrderByNumber(proParent.getId());
+
+                        // 存在二级属性,跳过
+                        if (sonProperties != null && sonProperties.size() > 0) {
+                            continue;
+                        } else {
+                            isSecondEmpty = false;
+                            // 保存试题属性
+                            QuesProperty quesProperty = new QuesProperty(proParent, null, courseProperty);
+                            String idNumber = quesProperty.getCoursePropertyName() + "-"
+                                    + quesProperty.getFirstProperty().getId();
+                            quesProperty.setId(idNumber);
+                            quesProperties.add(quesProperty);
+                        }
+                    }
+                }
+            }
+            if (isFirstEmpty) {
+                if (importPaperCheck.getNestedHeadNumber() != 0) {
+                    importPaperCheck
+                            .setErrorInfo(importPaperCheck.getQuesName() + "第" + importPaperCheck.getNestedHeadNumber()
+                                    + "个题" + subQuesNum + "中,小题一级属性值与课程属性中一级属性不匹配,请检查");
+                } else {
+                    importPaperCheck.setErrorInfo(
+                            getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,小题一级属性值与课程属性中一级属性不匹配,请检查");
+                }
+                throw new PaperException(importPaperCheck.getErrorInfo());
+            }
+
+            if (isSecondEmpty) {
+                if (importPaperCheck.getNestedHeadNumber() != 0) {
+                    importPaperCheck
+                            .setErrorInfo(importPaperCheck.getQuesName() + "第" + importPaperCheck.getNestedHeadNumber()
+                                    + "个题" + subQuesNum + "中,小题二级属性值与课程属性中二级属性不匹配,请检查");
+                } else {
+                    importPaperCheck.setErrorInfo(
+                            getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,小题二级属性值与课程属性中二级属性不匹配,请检查");
+                }
+                throw new PaperException(importPaperCheck.getErrorInfo());
+            }
+        } else {
+            // 一级,二级都为空
+        }
+    }
+
+    /**
+     * 校验小题尾信息是否含有"一节属性","二级属性","难度","公开"
+     *
+     * @param firstProperty
+     * @param secondProperty
+     * @param difficulty
+     * @param publicity
+     */
+    private void checkAttributeIsFull(String firstProperty, String secondProperty, Double difficulty, Boolean publicity,
+            ImportPaperCheck importPaperCheck, int subQuesNum) throws Exception {
+        if (firstProperty == null) {
+            if (importPaperCheck.getNestedHeadNumber() != 0) {
+                importPaperCheck.setErrorInfo(importPaperCheck.getQuesName() + "第"
+                        + importPaperCheck.getNestedHeadNumber() + "个题" + subQuesNum + "中,小题一级属性缺失,请检查");
+            } else {
+                importPaperCheck
+                        .setErrorInfo(getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,小题一级属性缺失,请检查");
+            }
+            throw new PaperException(importPaperCheck.getErrorInfo());
+        }
+
+        if (secondProperty == null) {
+            if (importPaperCheck.getNestedHeadNumber() != 0) {
+                importPaperCheck.setErrorInfo(importPaperCheck.getQuesName() + "第"
+                        + importPaperCheck.getNestedHeadNumber() + "个题" + subQuesNum + "中,小题二级属性缺失,请检查");
+            } else {
+                importPaperCheck
+                        .setErrorInfo(getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,小题二级属性缺失,请检查");
+            }
+            throw new PaperException(importPaperCheck.getErrorInfo());
+        }
+
+        if (difficulty == null) {
+            if (importPaperCheck.getNestedHeadNumber() != 0) {
+                importPaperCheck.setErrorInfo(importPaperCheck.getQuesName() + "第"
+                        + importPaperCheck.getNestedHeadNumber() + "个题" + subQuesNum + "中,小题难度缺失,请检查");
+            } else {
+                importPaperCheck
+                        .setErrorInfo(getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,小题难度缺失,请检查");
+            }
+            throw new PaperException(importPaperCheck.getErrorInfo());
+        }
+
+        if (publicity == null) {
+            if (importPaperCheck.getNestedHeadNumber() != 0) {
+                importPaperCheck.setErrorInfo(importPaperCheck.getQuesName() + "第"
+                        + importPaperCheck.getNestedHeadNumber() + "个题" + subQuesNum + "中,小题公开性缺失,请检查");
+            } else {
+                importPaperCheck
+                        .setErrorInfo(getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,小题公开性缺失,请检查");
+            }
+            throw new PaperException(importPaperCheck.getErrorInfo());
+        }
+    }
+
+    /**
+     * 处理选择题option信息
+     *
+     * @param question
+     */
+    private void processSelectOption(Question question) {
+        String answer = question.getQuesAnswer();
+        if (StringUtils.isNotBlank(answer)) {
+            String[] answerArray = answer.split(",");
+            for (int i = 0; i < question.getQuesOptions().size(); i++) {
+                QuesOption quesOption = question.getQuesOptions().get(i);
+                char number = (char) (Integer.parseInt(quesOption.getNumber()) + 64);
+                if (ArrayUtils.contains(answerArray, String.valueOf(number))) {
+                    quesOption.setIsCorrect((short) 1);
+                } else {
+                    quesOption.setIsCorrect((short) 0);
+                }
+            }
+        }
+    }
+
+    /**
+     * 处理套题
+     *
+     * @param pList
+     * @param index
+     * @param question
+     * @param paperDetailUnit
+     * @param importPaperCheck
+     * @throws Exception
+     */
+    private void processNestedQues(List<List<SectionElement>> pList, int index, Question question,
+            PaperDetailUnit paperDetailUnit, ImportPaperCheck importPaperCheck, Paper paper) throws Exception {
+        // 题型
+        String nestedQuesType = "";
+        // 处理套题的头信息
+        processNestedQuestionHead(pList, index, 0, question, importPaperCheck, paperDetailUnit);
+        // 设置套题题干
+        processQuesBody(pList, importPaperCheck.getIndex(), 0, question, importPaperCheck, 0);
+        // 创建小题集合
+        List<Question> subQuesList = new ArrayList<>();
+        Question subQues = null;
+        int beginNum = importPaperCheck.getIndex();
+        int quesTypeNum = 0;
+        int subQuesNum = 0;
+        boolean hasQuesType = false;
+        for (int i = beginNum; i < pList.size(); i++) {
+            List<SectionElement> pSubQues = pList.get(i);
+            String tmpText = JsonImportUtil.getPText(pSubQues);
+            if (StringUtils.isEmpty(tmpText)) {
+                continue;
+            }
+            if (tmpText.startsWith(ImportPaperMsg.subQuestionType_word)) {
+                nestedQuesType = getContent(tmpText, ImportPaperMsg.subQuestionType_word);
+                importPaperCheck.setIndex(importPaperCheck.getIndex() + 1);
+                quesTypeNum++;
+                hasQuesType = true;
+            } else if (tmpText.matches("^\\d{1,}\\.[\\s\\S]*")) {
+                ++subQuesNum;
+                if (!hasQuesType) {
+                    if (importPaperCheck.getNestedHeadNumber() == 0) {
+                        importPaperCheck.setErrorInfo(
+                                "[大题名称]:" + importPaperCheck.getQuesName() + "第" + subQuesNum + "题中,缺失[小题型],请检查套题");
+                    } else {
+                        importPaperCheck.setErrorInfo("[大题名称]:" + importPaperCheck.getQuesName() + "第"
+                                + importPaperCheck.getNestedHeadNumber() + "个套题中,第" + subQuesNum + "题缺失[小题型],请检查套题");
+                    }
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                if (StringUtils.isBlank(nestedQuesType) || getQuesStructType(nestedQuesType) == null) {
+                    importPaperCheck.setErrorInfo(importPaperCheck.getQuesName() + "第"
+                            + importPaperCheck.getNestedHeadNumber() + "个题" + subQuesNum + "中,"
+                            + ImportPaperMsg.errMsg_02 + "(" + ImportPaperMsg.errMsg_09 + ")");
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                subQues = new Question();
+                subQues.setId(IdUtils.uuid());
+                subQues.setQuestionType(getQuesStructType(nestedQuesType));
+                if (StringUtils.isNumeric(importPaperCheck.getQuesScore())) {
+                    subQues.setScore(Double.parseDouble(importPaperCheck.getQuesScore()));
+                }
+                // 处理客观题
+                if (nestedQuesType.equals(ImportPaperMsg.singleSelection)
+                        || nestedQuesType.equals(ImportPaperMsg.multipleSelection)) {
+                    // 处理题干
+                    processQuesBody(pList, importPaperCheck.getIndex(), subQuesNum, subQues, importPaperCheck,
+                            subQuesNum);
+                    // 处理选项
+                    processQuesOption(pList, importPaperCheck.getIndex(), subQuesNum, subQues, importPaperCheck,
+                            subQuesNum);
+                    // 处理尾信息
+                    processQuesTail(pList, importPaperCheck.getIndex(), subQuesNum, subQues, paperDetailUnit,
+                            importPaperCheck, true, paper, subQuesNum);
+                    // 处理选择题的option
+                    processSelectOption(subQues);
+                } else {
+                    // 处理其他题型
+                    processQuesBody(pList, importPaperCheck.getIndex(), subQuesNum, subQues, importPaperCheck,
+                            subQuesNum);
+                    processQuesTail(pList, importPaperCheck.getIndex(), subQuesNum, subQues, paperDetailUnit,
+                            importPaperCheck, true, paper, subQuesNum);
+                    // 填空题空格校验
+                    if (QuesStructType.FILL_BLANK_QUESTION.getName().equals(subQues.getQuestionType().getName())) {
+                        processFill(subQues, paperDetailUnit, importPaperCheck, subQuesNum, subQuesNum);
+                    }
+                }
+                subQuesList.add(subQues);
+                i = importPaperCheck.getIndex() - 1;
+                nestedQuesType = "";
+                hasQuesType = false;
+            } else if (subQuesNum != quesTypeNum) {
+                if (StringUtils.isEmpty(nestedQuesType)) {
+                    importPaperCheck.setErrorInfo(
+                            getQuesNumInfo(importPaperCheck.getQuesName(), quesTypeNum) + ImportPaperMsg.errMsg_02);
+                } else {
+                    importPaperCheck.setErrorInfo(
+                            getQuesNumInfo(importPaperCheck.getQuesName(), quesTypeNum) + ImportPaperMsg.errMsg_03);
+                }
+                throw new PaperException(importPaperCheck.getErrorInfo());
+            } else if (isHeader(tmpText)) {
+                break;
+            }
+            if (!StringUtils.isEmpty(importPaperCheck.getErrorInfo())) {
+                return;
+            }
+        }
+        // 判断标签是否丢失
+        if (importPaperCheck.getNestedQuesNumber() == 0 || importPaperCheck.getNestedHeadNumber() == 0) {
+            importPaperCheck.setErrorInfo(importPaperCheck.getQuesName() + "中,套题可能缺失【套题】和【套题数量】标签,请检查套题");
+            throw new PaperException(importPaperCheck.getErrorInfo());
+        }
+        // 判断子题数量是否一样
+        if (importPaperCheck.getNestedQuesNumber() != subQuesList.size()) {
+            importPaperCheck.setErrorInfo(importPaperCheck.getQuesName() + "第" + importPaperCheck.getNestedHeadNumber()
+                    + "个套题" + "中,套题数量与子题数量不一样,请检查套题");
+            throw new PaperException(importPaperCheck.getErrorInfo());
+        }
+        question.setSubQuestions(subQuesList);
+        // 设置套题主题干的 难度,公开度,一级属性,二级属性
+        setAttributes(question, subQuesList);
+        // 计算套题总分
+        double totalScore = 0;
+        List<Double> scoreList = new ArrayList<>();
+        for (Question child : subQuesList) {
+            Double score = child.getScore();
+            if (score != null) {
+                totalScore += score;
+                scoreList.add(score);
+            } else {
+                scoreList.add(0d);
+            }
+        }
+        question.setScore(totalScore);
+        paperDetailUnit.setSubScoreList(scoreList);
+    }
+
+    // 设置套题主题干的 难度,公开度,一级属性,二级属性
+    private void setAttributes(Question question, List<Question> subQuesList) {
+        if (subQuesList != null && subQuesList.size() > 0) {
+            Double totalDouble = 0d;
+            Double sum = 0.0;
+            Boolean publicity = false;
+            List<QuesProperty> subQuesProperties = new ArrayList<>();
+            for (Question subQuestion : subQuesList) {
+                // 设置一级属性,二级属性
+                List<QuesProperty> quesProperties = subQuestion.getQuesProperties();
+                if (quesProperties != null && quesProperties.size() > 0) {
+                    for (QuesProperty quesProperty : quesProperties) {
+                        subQuesProperties.add(quesProperty);
+                    }
+                }
+                // 设置公开度
+                if (subQuestion.getPublicity()) {
+                    publicity = true;
+                }
+                sum = subQuestion.getDifficultyDegree() * subQuestion.getScore() + sum;
+                totalDouble = subQuestion.getScore() + totalDouble;
+            }
+
+            BigDecimal b = BigDecimal.valueOf(sum / totalDouble);
+            Double difficulty = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
+            // 给属性去重
+            Set<QuesProperty> ts = new HashSet<>();
+            ts.addAll(subQuesProperties);
+            subQuesProperties.clear();
+            subQuesProperties.addAll(ts);
+            question.setDifficultyDegree(difficulty);
+            question.setPublicity(publicity);
+            question.setQuesProperties(subQuesProperties);
+            question.setDifficulty(setDiff(difficulty));
+        }
+    }
+
+    /**
+     * 获取试题结构类型
+     *
+     * @param quesType
+     * @return
+     */
+    private static QuesStructType getQuesStructType(String quesType) {
+        return CommonUtils.getEnum(QuesStructType.class, quesType);
+    }
+
+    /**
+     * 获取当前试题大题小题号
+     *
+     * @param quesType
+     * @param subQuesNum
+     * @return
+     */
+    private static String getQuesNumInfo(String quesType, int subQuesNum) {
+        return quesType + "第" + subQuesNum + "小题";
+    }
+
+    /**
+     * 判断是否为套题
+     *
+     * @param importPaperCheck
+     * @return
+     */
+    private static boolean isNested(ImportPaperCheck importPaperCheck) {
+        if (importPaperCheck.getQuesType().equals(ImportPaperMsg.nestedQuestion_word)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 校验试卷总分是否与设定一致
+     *
+     * @param paper
+     * @param paperDetailUnits
+     * @param importPaperCheck
+     * @throws PaperException
+     */
+    private void checkTotalScore(Paper paper, List<PaperDetailUnit> paperDetailUnits, ImportPaperCheck importPaperCheck)
+            throws PaperException {
+        double totalScore = 0;
+        for (PaperDetailUnit unit : paperDetailUnits) {
+            if (unit.getScore() != null) {
+                totalScore += unit.getScore();
+            }
+        }
+        if (paper.getTotalScore() != null && paper.getTotalScore() != totalScore) {
+            importPaperCheck.setErrorInfo("试卷总分(" + totalScore + ")与设置总分(" + paper.getTotalScore() + ")不符");
+            throw new PaperException(importPaperCheck.getErrorInfo());
+        }
+    }
+
+    /**
+     * 校验大题下小题数量是否与标识数量一致
+     *
+     * @throws PaperException
+     */
+    private void checkUnitNum(List<PaperDetailUnit> paperDetailUnits, ImportPaperCheck importPaperCheck)
+            throws PaperException {
+        Map<PaperDetail, Integer> unitNumMap = new HashMap<>();
+        Collections.sort(paperDetailUnits);
+
+        for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
+            PaperDetail key = paperDetailUnit.getPaperDetail();
+            if (unitNumMap.containsKey(key)) {
+                int value = unitNumMap.get(key);
+                /*
+                 * if (paperDetailUnit.getQuestionType() ==
+                 * QuesStructType.NESTED_ANSWER_QUESTION) { unitNumMap.put(key,
+                 * value + 1); } else { unitNumMap.put(key, value + 1); }
+                 */
+                unitNumMap.put(key, value + 1);
+            } else {
+                /*
+                 * if (paperDetailUnit.getQuestionType() ==
+                 * QuesStructType.NESTED_ANSWER_QUESTION) { unitNumMap.put(key,
+                 * 1); } else { unitNumMap.put(key, 1); }
+                 */
+                unitNumMap.put(key, 1);
+            }
+        }
+
+        for (Map.Entry<PaperDetail, Integer> entry : unitNumMap.entrySet()) {
+            PaperDetail paperDetail = entry.getKey();
+            if (paperDetail.getUnitCount().intValue() != entry.getValue().intValue()) {
+                importPaperCheck.setErrorInfo("大题:" + paperDetail.getName() + "标识小题数量(" + paperDetail.getUnitCount()
+                        + ")与实际小题数量(" + entry.getValue() + ")不符。" + "(注:若检查小题数量无问题,请检查word中题目的格式是否正确)");
+                throw new PaperException(importPaperCheck.getErrorInfo());
+            }
+        }
+    }
+
+    /**
+     * 校验选择题答案 1.判断答案格式,选择题答案必须以","分隔,且都为字母 2.判断答案在选项中是否不存在,例如选项为A,B,C,D,答案为E
+     *
+     * @param question
+     * @param answerWord
+     * @param importPaperCheck
+     * @throws PaperException
+     */
+    private void checkSelectAnswer(Question question, String answerWord, ImportPaperCheck importPaperCheck,
+            int subQuesNum) throws PaperException {
+        if (!StringUtils.isBlank(answerWord)) {
+            String[] pAnswerArray = answerWord.split(",");
+            List<QuesOption> options = question.getQuesOptions();
+            List<String> optionNumList = new ArrayList<>();
+            for (QuesOption quesOption : options) {
+                Integer numInteger = Integer.parseInt(quesOption.getNumber());
+                char word = (char) (numInteger + 64);
+                optionNumList.add(word + "");
+            }
+            for (String answer : pAnswerArray) {
+                answer = answer.trim();
+                String pattern = "[A-Z]|[a-z]";
+                if (!Pattern.matches(pattern, answer)) {
+                    importPaperCheck.setErrorInfo(
+                            getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,答案格式不正确,答案为:" + answerWord);
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+                if (!optionNumList.contains(answer)) {
+                    importPaperCheck.setErrorInfo(getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,选项为:"
+                            + optionNumList.toString() + ",答案为:" + answerWord + "。(注:若检查选项无问题,请检查word中题目的格式是否正确)");
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+            }
+        }
+    }
+
+    /**
+     * 校验判断题答案
+     *
+     * @param tmpText
+     * @param importPaperCheck
+     * @param subQuesNum
+     * @throws Exception
+     */
+    private void checkBoolAnswer(String tmpText, ImportPaperCheck importPaperCheck, int subQuesNum) throws Exception {
+        if (!StringUtils.isBlank(tmpText)) {
+            tmpText = tmpText.trim();
+            if (tmpText.equals("正确") || tmpText.equals("错误")) {
+                return;
+            } else {
+                importPaperCheck.setErrorInfo(
+                        getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,答案格式不正确,应为:正确或错误");
+                throw new PaperException(importPaperCheck.getErrorInfo());
+            }
+        }
+    }
+
+    /**
+     * 校验试题难度
+     */
+    private Double checkDifficulty(String tmpText, ImportPaperCheck importPaperCheck, int subQuesNum, Question question)
+            throws Exception {
+        Double tempDifficulty;
+        if (StringUtils.isBlank(tmpText)) {
+            // 如果为空,默认难度0.5
+            tempDifficulty = 0.5;
+            question.setDifficulty("中");
+        } else if (!isInteger(tmpText) || (Double.parseDouble(tmpText) < 1) || (Double.parseDouble(tmpText) > 10)) {
+            // 如果不是整数,或者在1到10之间,就报错
+            importPaperCheck
+                    .setErrorInfo(getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,试题难度只能是1到10之间整数");
+            throw new PaperException(importPaperCheck.getErrorInfo());
+        } else {
+            tempDifficulty = Double.parseDouble(tmpText) / 10;
+            question.setDifficulty(setDiff(tempDifficulty));
+        }
+
+        return tempDifficulty;
+    }
+
+    private String setDiff(Double difficulty) {
+        if (difficulty > 0 && difficulty < 0.4) {
+            return "难";
+        } else if (difficulty > 0.3 && difficulty < 0.8) {
+            return "中";
+        } else {
+            return "易";
+        }
+    }
+
+    /**
+     * 校验试题公开度
+     */
+    private Boolean checkPublicity(String tmpText, ImportPaperCheck importPaperCheck, int subQuesNum) throws Exception {
+        Boolean publicity;
+        if (StringUtils.isBlank(tmpText)) {
+            // 如果为空,默认是公开
+            publicity = true;
+        } else if (!tmpText.equals("公开") && !tmpText.equals("非公开")) {
+            // 如果不是公开和非公开,就报错
+            importPaperCheck
+                    .setErrorInfo(getQuesNumInfo(importPaperCheck.getQuesName(), subQuesNum) + "中,试题公开度只能是公开和非公开");
+            throw new PaperException(importPaperCheck.getErrorInfo());
+        } else {
+            if (tmpText.equals("非公开")) {
+                publicity = false;
+            } else {
+                publicity = true;
+            }
+        }
+        return publicity;
+    }
+
+    /**
+     * 校验客观题答案完整性 客观题答案要么全有,要么全没有
+     *
+     * @throws PaperException
+     */
+    private void checkAnswerISfull(List<PaperDetailUnit> paperDetailUnits, ImportPaperCheck importPaperCheck)
+            throws PaperException {
+        Map<Question, PaperDetailUnit> map = new HashMap<>();
+        for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
+            Question question = paperDetailUnit.getQuestion();
+            if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+                    || question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION
+                    || question.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION) {
+                map.put(question, paperDetailUnit);
+            }
+            if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+                List<Question> subQuestions = question.getSubQuestions();
+                for (Question subQuestion : subQuestions) {
+                    if (subQuestion.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+                            || subQuestion.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION
+                            || subQuestion.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION) {
+                        map.put(subQuestion, paperDetailUnit);
+                    }
+                }
+            }
+        }
+        boolean isNull = false;
+        boolean isNotNull = false;
+        PaperDetailUnit paperDetailUnit = new PaperDetailUnit();
+        Question subQuestion = new Question();
+        for (Question question : map.keySet()) {
+            PaperDetailUnit pdu = map.get(question);
+            if (StringUtils.isBlank(question.getQuesAnswer())) {
+                isNull = true;
+                if (pdu.getQuestion().getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+                    subQuestion = question;
+                }
+                paperDetailUnit = pdu;
+                break;
+            }
+        }
+        for (Question question : map.keySet()) {
+            // PaperDetailUnit pdu = map.get(question);
+            if (StringUtils.isNotBlank(question.getQuesAnswer())) {
+                isNotNull = true;
+                break;
+            }
+        }
+        // 不满足(客观题答案要么全有,要么全没有)条件
+        if (isNull && isNotNull) {
+            if (paperDetailUnit.getQuestion().getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+                int number = 0;
+                for (int i = 0; i < paperDetailUnit.getQuestion().getSubQuestions().size(); i++) {
+                    if (paperDetailUnit.getQuestion().getSubQuestions().get(i).getId().equals(subQuestion.getId())) {
+                        number = i + 1;
+                    }
+                }
+                importPaperCheck.setErrorInfo("第" + paperDetailUnit.getNumber() + "大题套题中" + number + "小题答案缺失,请检查");
+                throw new PaperException(importPaperCheck.getErrorInfo());
+            }
+            importPaperCheck.setErrorInfo("第" + paperDetailUnit.getNumber() + "小题答案缺失,请检查");
+            throw new PaperException(importPaperCheck.getErrorInfo());
+        }
+    }
+
+    /**
+     * 替换字符串中的属性头得到属性值
+     *
+     * @param pText
+     * @param replaceContent
+     * @return
+     */
+    private static String getContent(String pText, String replaceContent) {
+        String word = replaceContent.replace("[", "").replace("]", "");
+        return pText.replaceAll("\\[" + word + "\\]", "").replaceAll("[:|:]", "").trim();
+    }
+
+    private static boolean isInteger(String str) {
+        Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");
+        return pattern.matcher(str).matches();
+    }
+
+    /**
+     * 判断填空题题干和答案空格数量是否一致
+     *
+     * @throws PaperException
+     */
+    private void processFill(Question question, PaperDetailUnit paperDetailUnit, ImportPaperCheck importPaperCheck,
+            int subQuesNum, int errorQuesNum) throws PaperException {
+        // 按3个#号截取题干
+        String[] quesBody = question.getQuesBody().split("###");
+        String[] quesAnwser = question.getQuesAnswer().split("##");
+        // 如果有答案,进行判断
+        if (quesAnwser.length > 0) {
+            String str = quesAnwser[0].replaceAll("<p>", "").replaceAll("</p>", "").replaceAll("<span>", "")
+                    .replaceAll("</span>", "").replace("&nbsp;", "");
+            if (quesAnwser.length == 1 && !StringUtils.isBlank(str) || quesAnwser.length > 1) {
+                // 判断题干空格和答案空格是否相等
+                if ((quesBody.length - 1) != quesAnwser.length) {
+                    if (importPaperCheck.getNestedHeadNumber() == 0) {
+                        importPaperCheck.setErrorInfo(
+                                "[大题名称]:" + importPaperCheck.getQuesName() + "第" + errorQuesNum + "题中,题干与答案的空格数量不一样");
+                    } else {
+                        importPaperCheck.setErrorInfo("[大题名称]:" + importPaperCheck.getQuesName() + "第"
+                                + importPaperCheck.getNestedHeadNumber() + "个套题中,第" + errorQuesNum
+                                + "题中,题干与答案的空格数量不一样");
+                    }
+                    throw new PaperException(importPaperCheck.getErrorInfo());
+                }
+            }
+        }
+    }
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 306 - 177
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/ImportPaperService.java


Vissa filer visades inte eftersom för många filer har ändrats