Browse Source

add RichTextConverter

deason 3 years ago
parent
commit
3888924271

+ 70 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/handler/richtext2/Block.java

@@ -0,0 +1,70 @@
+package cn.com.qmth.examcloud.support.handler.richtext2;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.io.Serializable;
+
+/**
+ * 内容块
+ */
+public class Block implements Serializable {
+
+    private static final long serialVersionUID = -8820818435363871114L;
+
+    private BlockType type;// 块类型
+
+    private String value;// 块内容
+
+    private Param param;// 样式参数
+
+    private Control control;// 控制参数
+
+    private Integer playTime;// 播放次数
+
+    public BlockType getType() {
+        return type;
+    }
+
+    public void setType(BlockType type) {
+        this.type = type;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public Param getParam() {
+        return param;
+    }
+
+    public void setParam(Param param) {
+        this.param = param;
+    }
+
+    public Control getControl() {
+        return control;
+    }
+
+    public void setControl(Control control) {
+        this.control = control;
+    }
+
+    public Integer getPlayTime() {
+        return playTime;
+    }
+
+    public void setPlayTime(Integer playTime) {
+        this.playTime = playTime;
+    }
+
+    @Override
+    public String toString() {
+        return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
+    }
+
+}

+ 26 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/handler/richtext2/BlockType.java

@@ -0,0 +1,26 @@
+package cn.com.qmth.examcloud.support.handler.richtext2;
+
+/**
+ * 内容块-类型
+ */
+public enum BlockType {
+
+    text("文本"),
+
+    image("图片"),
+
+    audio("音频"),
+
+    video("视频");
+
+    private String description;
+
+    BlockType(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+}

+ 40 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/handler/richtext2/Control.java

@@ -0,0 +1,40 @@
+package cn.com.qmth.examcloud.support.handler.richtext2;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.io.Serializable;
+
+/**
+ * 控制参数
+ */
+public class Control implements Serializable {
+
+    private static final long serialVersionUID = -8820818435363871114L;
+
+    private Integer maxPlayCount;//最大播放次数
+
+    private Integer fixedPlayInterval;//固定播放间隔,单位为秒
+
+    public Integer getMaxPlayCount() {
+        return maxPlayCount;
+    }
+
+    public void setMaxPlayCount(Integer maxPlayCount) {
+        this.maxPlayCount = maxPlayCount;
+    }
+
+    public Integer getFixedPlayInterval() {
+        return fixedPlayInterval;
+    }
+
+    public void setFixedPlayInterval(Integer fixedPlayInterval) {
+        this.fixedPlayInterval = fixedPlayInterval;
+    }
+
+    @Override
+    public String toString() {
+        return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
+    }
+
+}

+ 100 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/handler/richtext2/Param.java

@@ -0,0 +1,100 @@
+package cn.com.qmth.examcloud.support.handler.richtext2;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.io.Serializable;
+
+/**
+ * 样式参数
+ */
+public class Param implements Serializable {
+
+    private static final long serialVersionUID = -8820818435363871114L;
+
+    private String width;//宽
+
+    private String height;//高
+
+    private Boolean bold;//加粗
+
+    private Boolean underline;//下划线
+
+    private Boolean italic;//斜体
+
+    private Boolean sup;//上标
+
+    private Boolean sub;//下标
+
+    private Integer duration;//时长,单位为秒
+
+    public String getWidth() {
+        return width;
+    }
+
+    public void setWidth(String width) {
+        this.width = width;
+    }
+
+    public String getHeight() {
+        return height;
+    }
+
+    public void setHeight(String height) {
+        this.height = height;
+    }
+
+    public Boolean getBold() {
+        return bold;
+    }
+
+    public void setBold(Boolean bold) {
+        this.bold = bold;
+    }
+
+    public Boolean getUnderline() {
+        return underline;
+    }
+
+    public void setUnderline(Boolean underline) {
+        this.underline = underline;
+    }
+
+    public Boolean getItalic() {
+        return italic;
+    }
+
+    public void setItalic(Boolean italic) {
+        this.italic = italic;
+    }
+
+    public Boolean getSup() {
+        return sup;
+    }
+
+    public void setSup(Boolean sup) {
+        this.sup = sup;
+    }
+
+    public Boolean getSub() {
+        return sub;
+    }
+
+    public void setSub(Boolean sub) {
+        this.sub = sub;
+    }
+
+    public Integer getDuration() {
+        return duration;
+    }
+
+    public void setDuration(Integer duration) {
+        this.duration = duration;
+    }
+
+    @Override
+    public String toString() {
+        return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
+    }
+
+}

+ 42 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/handler/richtext2/Result.java

@@ -0,0 +1,42 @@
+package cn.com.qmth.examcloud.support.handler.richtext2;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Result implements Serializable {
+
+    private static final long serialVersionUID = -8820818435363871114L;
+
+    private List<Section> sections;
+
+    public void addSection(Section section) {
+        if (section == null) {
+            return;
+        }
+        if (sections == null) {
+            sections = new ArrayList<>();
+        }
+        sections.add(section);
+    }
+
+    public List<Section> getSections() {
+        if (sections == null) {
+            sections = new ArrayList<>();
+        }
+        return sections;
+    }
+
+    public void setSections(List<Section> sections) {
+        this.sections = sections;
+    }
+
+    @Override
+    public String toString() {
+        return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
+    }
+
+}

+ 257 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/handler/richtext2/RichTextConverter.java

@@ -0,0 +1,257 @@
+package cn.com.qmth.examcloud.support.handler.richtext2;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.Node;
+import org.jsoup.nodes.TextNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * HTML结构转换为“富文本”JSON结构
+ * 注:特殊定制,只处理业务所需的标签和结构,如:试题题干内容
+ *
+ * @author: Deason
+ * @since: 2021/9/10
+ */
+public class RichTextConverter implements TagConstant {
+
+    private final static Logger log = LoggerFactory.getLogger(RichTextConverter.class);
+
+    public static Result parse(String html) {
+        if (StringUtils.isEmpty(html)) {
+            return new Result();
+        }
+
+        Document document = Jsoup.parse(html);
+        return parse(document);
+    }
+
+    public static Result parse(Document document) {
+        Element body = document.body();
+        List<Node> nodes = body.childNodes();
+
+        Result result = new Result();
+        result.setSections(splitSections(nodes));
+        return result;
+    }
+
+    private static List<Section> splitSections(List<Node> nodes) {
+        if (CollectionUtils.isEmpty(nodes)) {
+            return new ArrayList<>();
+        }
+
+        List<List<Node>> groups = new ArrayList<>();
+
+        if (nodes.size() == 1) {
+            groups.add(nodes);
+        } else {
+            List<Node> tempNodes = new ArrayList<>();
+
+            for (int n = 0; n < nodes.size(); n++) {
+                Node node = nodes.get(n);
+
+                if (TAG_P.equals(node.nodeName())) {
+                    if (!CollectionUtils.isEmpty(tempNodes)) {
+                        // 先将“当前p标签”之前的元素作为一组
+                        groups.add(new ArrayList<>(tempNodes));
+                        tempNodes.clear();
+                    }
+
+                    // 再将“当前p标签”的元素作为一组
+                    tempNodes.add(node);
+                    groups.add(new ArrayList<>(tempNodes));
+                    tempNodes.clear();
+                } else {
+                    // 非“p标签”元素
+                    tempNodes.add(node);
+                }
+
+                if (n == (nodes.size() - 1) && !CollectionUtils.isEmpty(tempNodes)) {
+                    // 最后一个元素 且 非“p标签”元素
+                    groups.add(tempNodes);
+                }
+            }
+        }
+
+        List<Section> sections = new ArrayList<>();
+        for (List<Node> curNodes : groups) {
+            List<Block> blocks = new ArrayList<>();
+            splitBlocks(curNodes, blocks);
+            if (blocks.isEmpty()) {
+                continue;
+            }
+
+            Section section = new Section();
+            section.setBlocks(blocks);
+            sections.add(section);
+        }
+        groups.clear();
+
+        return sections;
+    }
+
+    private static void splitBlocks(List<Node> nodes, List<Block> blocks) {
+        if (nodes.isEmpty()) {
+            return;
+        }
+
+        for (Node node : nodes) {
+            if (node instanceof Element) {
+                Element element = (Element) node;
+
+                if (TAG_IMG.equals(node.nodeName())) {
+                    // 处理图片
+                    processImg(element, blocks);
+                } else if (TAG_A.equals(node.nodeName())) {
+                    // 处理音频
+                    processAudio(element, blocks);
+                } else if (TAG_B.equals(node.nodeName()) || TAG_STRONG.equals(node.nodeName())) {
+                    // 处理文本 加粗
+                    Param param = new Param();
+                    param.setBold(true);
+                    processText(element.text(), param, blocks);
+                } else if (TAG_I.equals(node.nodeName()) || TAG_EM.equals(node.nodeName())) {
+                    // 处理文本 斜体
+                    Param param = new Param();
+                    param.setItalic(true);
+                    processText(element.text(), param, blocks);
+                } else if (TAG_U.equals(node.nodeName())) {
+                    // 处理文本 下划线
+                    Param param = new Param();
+                    param.setUnderline(true);
+                    processText(element.text(), param, blocks);
+                } else if (TAG_SUP.equals(node.nodeName())) {
+                    // 处理文本 上标
+                    Param param = new Param();
+                    param.setSup(true);
+                    processText(element.text(), param, blocks);
+                } else if (TAG_SUB.equals(node.nodeName())) {
+                    // 处理文本 下标
+                    Param param = new Param();
+                    param.setSub(true);
+                    processText(element.text(), param, blocks);
+                } else {
+                    splitBlocks(node.childNodes(), blocks);
+                }
+            } else {
+                if (node instanceof TextNode) {
+                    TextNode element = (TextNode) node;
+                    // 处理文本
+                    processText(element.text(), null, blocks);
+                } else {
+                    log.debug("Ignore node {}", node.nodeName());
+                }
+            }
+        }
+    }
+
+    private static void processImg(Element element, List<Block> blocks) {
+        String src = element.attr(ATTR_SRC);
+        if (StringUtils.isBlank(src)) {
+            return;
+        }
+
+        String width = element.attr(ATTR_WIDTH);
+        String height = element.attr(ATTR_HEIGHT);
+        String style = element.attr(ATTR_STYLE);
+        if (StringUtils.isBlank(width)) {
+            width = extractStyle(style, ATTR_WIDTH);
+        }
+        if (StringUtils.isBlank(height)) {
+            height = extractStyle(style, ATTR_HEIGHT);
+        }
+
+        Param param = new Param();
+        param.setWidth(extractNumber(width));
+        param.setHeight(extractNumber(height));
+
+        Block block = new Block();
+        block.setType(BlockType.image);
+        block.setValue(src);
+        block.setParam(param);
+        blocks.add(block);
+    }
+
+    private static void processAudio(Element element, List<Block> blocks) {
+        String id = element.attr(ATTR_ID);
+        String name = element.attr(ATTR_NAME);
+        if (StringUtils.isBlank(id) || StringUtils.isBlank(name) || !name.endsWith(AUDIO_SUFFIX)) {
+            // 按文本处理
+            // processText(element.text(), null, blocks);
+            return;
+        }
+
+        Block block = new Block();
+        block.setType(BlockType.audio);
+        block.setValue(id + AUDIO_SUFFIX);
+        blocks.add(block);
+    }
+
+    private static void processText(String value, Param param, List<Block> blocks) {
+        if (StringUtils.isBlank(value)) {
+            return;
+        }
+
+        Block block = new Block();
+        block.setType(BlockType.text);
+        block.setValue(value);
+        block.setParam(param);
+        blocks.add(block);
+    }
+
+    private static String extractStyle(String style, String attributeName) {
+        if (StringUtils.isBlank(style)) {
+            return "";
+        }
+
+        style = style.toLowerCase();
+        if (!style.contains(attributeName)) {
+            return "";
+        }
+
+        // 截取样式内属性值
+        String[] attributes = style.split(";");
+        for (String attribute : attributes) {
+            if (attribute.trim().startsWith(attributeName)) {
+                return attribute.replaceFirst(attributeName, "")
+                        .replaceFirst(":", "")
+                        .trim();
+            }
+        }
+
+        return "";
+    }
+
+    private static String extractNumber(String str) {
+        if (StringUtils.isBlank(str)) {
+            return "";
+        }
+
+        // 截取数字
+        Pattern pattern = Pattern.compile("\\d+");
+        Matcher matcher = pattern.matcher(str);
+        while (matcher.find()) {
+            return matcher.group(0);
+        }
+        return "";
+    }
+
+    private static String replaceTags(String str) {
+        str = str.replace("&nbsp;", "");//消除空格
+        str = str.replace("&quot;", "\"");//将&quot;转换成"
+        str = str.replace("&lt;", "<");//将&lt;转换成<
+        str = str.replace("&gt;", ">");//将&gt;转换成>
+        str = str.replace("&amp;", "&");//将&amp;转换成&
+        return str;
+    }
+
+}

+ 45 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/handler/richtext2/Section.java

@@ -0,0 +1,45 @@
+package cn.com.qmth.examcloud.support.handler.richtext2;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 段落
+ */
+public class Section implements Serializable {
+
+    private static final long serialVersionUID = -8820818435363871114L;
+
+    private List<Block> blocks;
+
+    public void addBlock(Block block) {
+        if (block == null) {
+            return;
+        }
+        if (blocks == null) {
+            blocks = new ArrayList<>();
+        }
+        blocks.add(block);
+    }
+
+    public List<Block> getBlocks() {
+        if (blocks == null) {
+            blocks = new ArrayList<>();
+        }
+        return blocks;
+    }
+
+    public void setBlocks(List<Block> blocks) {
+        this.blocks = blocks;
+    }
+
+    @Override
+    public String toString() {
+        return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
+    }
+
+}

+ 42 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/handler/richtext2/TagConstant.java

@@ -0,0 +1,42 @@
+package cn.com.qmth.examcloud.support.handler.richtext2;
+
+public interface TagConstant {
+
+    String TAG_P = "p";// p标签
+
+    String TAG_A = "a";// a标签
+
+    String TAG_STRONG = "strong";// 加粗标签
+
+    String TAG_B = "b";// 加粗标签
+
+    String TAG_EM = "em";// 斜体标签
+
+    String TAG_I = "i";// 斜体标签
+
+    String TAG_U = "u";// 下划线标签
+
+    String TAG_SUP = "sup";// 上标标签
+
+    String TAG_SUB = "sub";// 下标标签
+
+    String TAG_IMG = "img";// img标签
+
+    String ATTR_SRC = "src";
+
+    String ATTR_STYLE = "style";
+
+    String ATTR_WIDTH = "width";
+
+    String ATTR_HEIGHT = "height";
+
+    String ATTR_ID = "id";
+
+    String ATTR_NAME = "name";
+
+    String AUDIO_SUFFIX = ".mp3";
+
+    String VIDEO_SUFFIX = ".mp4";
+
+
+}