apache poi_将HTML转换为Apache POI的RichTextString

apache poi

1.概述

在本教程中,我们将构建一个将HTML作为输入的应用程序,并使用提供HTML的RichText表示形式创建Microsoft Excel工作簿。 为了生成Microsoft Excel工作簿,我们将使用Apache POI 。 为了分析HTML,我们将使用Jericho。

Github上提供了本教程的完整源代码。

2.什么是耶利哥?

Jericho是一个Java库 ,它允许对HTML文档的各个部分(包括服务器端标签)进行分析和操作,同时逐字再现任何无法识别或无效HTML。 它还提供了高级HTML表单操作功能。 它是一个开放源代码库,使用以下许可证发行: Eclipse公共许可证(EPL) , GNU通用公共许可证(LGPL)和Apache许可证 。

我发现Jericho非常易于使用,可以实现将HTML转换为RichText的目标。

3. pom.xml

这是我们正在构建的应用程序所需的依赖项。 请注意,对于此应用程序,我们必须使用Java 9 。 这是因为我们使用的java.util.regex appendReplacement方法自Java 9起才可用。

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.9.RELEASE</version><relativePath /> <!-- lookup parent from repository -->
</parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>9</java.version>
</properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-batch</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.7</version></dependency><dependency><groupId>org.springframework.batch</groupId><artifactId>spring-batch-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.15</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.15</version></dependency><!-- https://mvnrepository.com/artifact/net.htmlparser.jericho/jericho-html --><dependency><groupId>net.htmlparser.jericho</groupId><artifactId>jericho-html</artifactId><version>3.4</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><!-- legacy html allow --><dependency><groupId>net.sourceforge.nekohtml</groupId><artifactId>nekohtml</artifactId></dependency>
</dependencies>

4.网页– Thymeleaf

我们使用Thymeleaf来创建一个基本页面,该页面具有带有文本区域的表单。 Github上提供 Thymeleaf页面的源代码。 如果愿意,可以使用RichText编辑器替换此textarea,例如CKEditor。 我们只需要注意使用适当的setData方法使AJAX的数据正确即可。 在Spring Boot中,以前有一个关于CKeditor的教程,标题为CKEditor,名为AJAX 。

5.控制器

在我们的控制器中,我们将自动装配JobLauncher和一个Spring Batch作业,我们将创建一个名为GenerateExcel的作业 。 通过自动装配这两个类,当POST请求发送到“ / export”时,我们可以按需运行Spring Batch Job GenerateExcel

要注意的另一件事是,为了确保Spring Batch作业将运行一次以上,我们在此代码中包含唯一参数: addLong(“ uniqueness”,System.nanoTime())。toJobParameters() 。 如果我们不包括唯一参数,则可能会发生错误,因为只能创建和执行唯一的JobInstances,否则Spring Batch无法区分第一个JobInstance和第二个JobInstance

@Controller
public class WebController {private String currentContent;@AutowiredJobLauncher jobLauncher;@AutowiredGenerateExcel exceljob; @GetMapping("/")public ModelAndView getHome() {ModelAndView modelAndView = new ModelAndView("index");return modelAndView;}@PostMapping("/export")public String postTheFile(@RequestBody String body, RedirectAttributes redirectAttributes, Model model)throws IOException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException {setCurrentContent(body);Job job = exceljob.ExcelGenerator();jobLauncher.run(job, new JobParametersBuilder().addLong("uniqueness", System.nanoTime()).toJobParameters());return "redirect:/";}//standard getters and setters}

6.批处理作业

在批处理作业的步骤1中,我们调用getCurrentContent()方法来获取传递到Thymeleaf表单中的内容,创建一个新的XSSFWorkbook,指定一个任意的Microsoft Excel Sheet选项卡名称,然后将所有三个变量都传递到createWorksheet方法中我们将在本教程的下一步中进行以下操作:

@Configuration
@EnableBatchProcessing
@Lazy
public class GenerateExcel {List<String> docIds = new ArrayList<String>();@Autowiredprivate JobBuilderFactory jobBuilderFactory;@Autowiredprivate StepBuilderFactory stepBuilderFactory;@AutowiredWebController webcontroller;@AutowiredCreateWorksheet createexcel;@Beanpublic Step step1() {return stepBuilderFactory.get("step1").tasklet(new Tasklet() {@Overridepublic RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception, JSONException {String content = webcontroller.getCurrentContent();System.out.println("content is ::" + content);Workbook wb = new XSSFWorkbook();String tabName = "some";createexcel.createWorkSheet(wb, content, tabName);return RepeatStatus.FINISHED;}}).build();}@Beanpublic Job ExcelGenerator() {return jobBuilderFactory.get("ExcelGenerator").start(step1()).build();}}

我们还在其他教程中介绍了Spring Batch,例如将XML转换为JSON + Spring Batch和Spring Batch CSV Processing 。

7. Excel创建服务

我们使用各种类来创建我们的Microsoft Excel文件。 在将HTML转换为RichText时,顺序很重要,因此这将是重点。

7.1 RichTextDetails

一个带有两个参数的类:一个字符串,其内容将成为RichText,一个字体映射。

public class RichTextDetails {private String richText;private Map<Integer, Font> fontMap;//standard getters and setters@Overridepublic int hashCode() {// The goal is to have a more efficient hashcode than standard one.return richText.hashCode();}

7.2 RichTextInfo

一个POJO,它将跟踪RichText的位置以及其他内容:

public class RichTextInfo {private int startIndex;private int endIndex;private STYLES fontStyle;private String fontValue;// standard getters and setters, and the like

7.3样式

一个包含要处理HTML标记的枚举。 我们可以根据需要添加以下内容:

public enum STYLES {BOLD("b"), EM("em"), STRONG("strong"), COLOR("color"), UNDERLINE("u"), SPAN("span"), ITALLICS("i"), UNKNOWN("unknown"),PRE("pre");// standard getters and setters

7.4 TagInfo

POJO跟踪标签信息:

public class TagInfo {private String tagName;private String style;private int tagType;// standard getters and setters

7.5 HTML为RichText

这不是一个小类,所以让我们按方法将其分解。

本质上,我们用div标签将任意HTML包围起来,因此我们知道我们在寻找什么。 然后,我们在div标签中查找所有元素,将每个元素添加到RichTextDetails的ArrayList中,然后将整个ArrayList传递给mergeTextDetails方法。 mergeTextDetails返回RichtextString,这是我们需要设置单元格值的内容:

public RichTextString fromHtmlToCellValue(String html, Workbook workBook){Config.IsHTMLEmptyElementTagRecognised = true;Matcher m = HEAVY_REGEX.matcher(html);String replacedhtml =  m.replaceAll("");StringBuilder sb = new StringBuilder();sb.insert(0, "<div>");sb.append(replacedhtml);sb.append("</div>");String newhtml = sb.toString();Source source = new Source(newhtml);List<RichTextDetails> cellValues = new ArrayList<RichTextDetails>();for(Element el : source.getAllElements("div")){cellValues.add(createCellValue(el.toString(), workBook));}RichTextString cellValue = mergeTextDetails(cellValues);return cellValue;}

如上所述,我们在此方法中传递了RichTextDetails的ArrayList。 Jericho的设置采用布尔值来识别空标签元素,例如
:已识别Config.IsHTMLEmptyElementTag。 在与在线富文本编辑器打交道时,这可能很重要,因此我们将其设置为true。 因为我们需要跟踪元素的顺序,所以我们使用LinkedHashMap而不是HashMap。

private static RichTextString mergeTextDetails(List<RichTextDetails> cellValues) {Config.IsHTMLEmptyElementTagRecognised = true;StringBuilder textBuffer = new StringBuilder();Map<Integer, Font> mergedMap = new LinkedHashMap<Integer, Font>(550, .95f);int currentIndex = 0;for (RichTextDetails richTextDetail : cellValues) {//textBuffer.append(BULLET_CHARACTER + " ");currentIndex = textBuffer.length();for (Entry<Integer, Font> entry : richTextDetail.getFontMap().entrySet()) {mergedMap.put(entry.getKey() + currentIndex, entry.getValue());}textBuffer.append(richTextDetail.getRichText()).append(NEW_LINE);}RichTextString richText = new XSSFRichTextString(textBuffer.toString());for (int i = 0; i < textBuffer.length(); i++) {Font currentFont = mergedMap.get(i);if (currentFont != null) {richText.applyFont(i, i + 1, currentFont);}}return richText;}

如上所述,我们使用Java 9来将StringBuilder与java.util.regex.Matcher.appendReplacement结合使用 。 为什么? 那是因为StringBuffer的运行速度比StringBuilder慢。 StringBuffer函数被同步以确保线程安全,因此速度较慢。

我们使用Deque而不是Stack,因为Deque接口提供了更完整和一致的LIFO堆栈操作集:

static RichTextDetails createCellValue(String html, Workbook workBook) {Config.IsHTMLEmptyElementTagRecognised  = true;Source source = new Source(html);Map<String, TagInfo> tagMap = new LinkedHashMap<String, TagInfo>(550, .95f);for (Element e : source.getChildElements()) {getInfo(e, tagMap);}StringBuilder sbPatt = new StringBuilder();sbPatt.append("(").append(StringUtils.join(tagMap.keySet(), "|")).append(")");String patternString = sbPatt.toString();Pattern pattern = Pattern.compile(patternString);Matcher matcher = pattern.matcher(html);StringBuilder textBuffer = new StringBuilder();List<RichTextInfo> textInfos = new ArrayList<RichTextInfo>();ArrayDeque<RichTextInfo> richTextBuffer = new ArrayDeque<RichTextInfo>();while (matcher.find()) {matcher.appendReplacement(textBuffer, "");TagInfo currentTag = tagMap.get(matcher.group(1));if (START_TAG == currentTag.getTagType()) {richTextBuffer.push(getRichTextInfo(currentTag, textBuffer.length(), workBook));} else {if (!richTextBuffer.isEmpty()) {RichTextInfo info = richTextBuffer.pop();if (info != null) {info.setEndIndex(textBuffer.length());textInfos.add(info);}}}}matcher.appendTail(textBuffer);Map<Integer, Font> fontMap = buildFontMap(textInfos, workBook);return new RichTextDetails(textBuffer.toString(), fontMap);}

我们可以在这里看到RichTextInfo的使用位置:

private static Map<Integer, Font> buildFontMap(List<RichTextInfo> textInfos, Workbook workBook) {Map<Integer, Font> fontMap = new LinkedHashMap<Integer, Font>(550, .95f);for (RichTextInfo richTextInfo : textInfos) {if (richTextInfo.isValid()) {for (int i = richTextInfo.getStartIndex(); i < richTextInfo.getEndIndex(); i++) {fontMap.put(i, mergeFont(fontMap.get(i), richTextInfo.getFontStyle(), richTextInfo.getFontValue(), workBook));}}}return fontMap;}

我们在哪里使用STYLES枚举:

private static Font mergeFont(Font font, STYLES fontStyle, String fontValue, Workbook workBook) {if (font == null) {font = workBook.createFont();}switch (fontStyle) {case BOLD:case EM:case STRONG:font.setBoldweight(Font.BOLDWEIGHT_BOLD);break;case UNDERLINE:font.setUnderline(Font.U_SINGLE);break;case ITALLICS:font.setItalic(true);break;case PRE:font.setFontName("Courier New");case COLOR:if (!isEmpty(fontValue)) {font.setColor(IndexedColors.BLACK.getIndex());}break;default:break;}return font;}

我们正在使用TagInfo类来跟踪当前标签:

private static RichTextInfo getRichTextInfo(TagInfo currentTag, int startIndex, Workbook workBook) {RichTextInfo info = null;switch (STYLES.fromValue(currentTag.getTagName())) {case SPAN:if (!isEmpty(currentTag.getStyle())) {for (String style : currentTag.getStyle().split(";")) {String[] styleDetails = style.split(":");if (styleDetails != null && styleDetails.length > 1) {if ("COLOR".equalsIgnoreCase(styleDetails[0].trim())) {info = new RichTextInfo(startIndex, -1, STYLES.COLOR, styleDetails[1]);}}}}break;default:info = new RichTextInfo(startIndex, -1, STYLES.fromValue(currentTag.getTagName()));break;}return info;}

我们处理HTML标签:

private static void getInfo(Element e, Map<String, TagInfo> tagMap) {tagMap.put(e.getStartTag().toString(),new TagInfo(e.getStartTag().getName(), e.getAttributeValue("style"), START_TAG));if (e.getChildElements().size() > 0) {List<Element> children = e.getChildElements();for (Element child : children) {getInfo(child, tagMap);}}if (e.getEndTag() != null) {tagMap.put(e.getEndTag().toString(),new TagInfo(e.getEndTag().getName(), END_TAG));} else {// Handling self closing tagstagMap.put(e.getStartTag().toString(),new TagInfo(e.getStartTag().getName(), END_TAG));}}

7.6创建工作表

使用StringBuilder,我创建了一个要写入FileOutPutStream的字符串。 在实际应用中,应由用户定义。 我在两个不同的行上附加了文件夹路径和文件名。 请将文件路径更改为您自己的文件路径。

sheet.createRow(0)在第一行创建一行,而dataRow.createCell(0)在该行的列A中创建一个单元格。

public void createWorkSheet(Workbook wb, String content, String tabName) {StringBuilder sbFileName = new StringBuilder();sbFileName.append("/Users/mike/javaSTS/michaelcgood-apache-poi-richtext/");sbFileName.append("myfile.xlsx");String fileMacTest = sbFileName.toString();try {this.fileOut = new FileOutputStream(fileMacTest);} catch (FileNotFoundException ex) {Logger.getLogger(CreateWorksheet.class.getName()).log(Level.SEVERE, null, ex);}Sheet sheet = wb.createSheet(tabName); // Create new sheet w/ Tab namesheet.setZoom(85); // Set sheet zoom: 85%// content rich textRichTextString contentRich = null;if (content != null) {contentRich = htmlToExcel.fromHtmlToCellValue(content, wb);}// begin insertion of values into cellsRow dataRow = sheet.createRow(0);Cell A = dataRow.createCell(0); // Row NumberA.setCellValue(contentRich);sheet.autoSizeColumn(0);try {/// Write the output to a filewb.write(fileOut);fileOut.close();} catch (IOException ex) {Logger.getLogger(CreateWorksheet.class.getName()).log(Level.SEVERE, null, ex);}}

8.演示

我们访问localhost:8080

我们用一些HTML输入一些文本:

我们打开excel文件,然后看到我们创建的RichText:

9.结论

我们可以看到将HTML转换为Apache POI的RichTextString类并不是一件容易的事。 但是,对于商业应用程序而言,将HTML转换为RichTextString至关重要,因为在Microsoft Excel文件中,可读性很重要。 我们构建的应用程序的性能可能还有改进的余地,但我们涵盖了构建此类应用程序的基础。

完整的源代码可在Github上找到。

翻译自: https://www.javacodegeeks.com/2018/01/converting-html-richtextstring-apache-poi.html

apache poi

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/333911.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows 下有哪些逆天的软件?

文章目录逆天软件系列1&#xff1a;Everything逆天软件系列2&#xff1a;Total Commander逆天软件系列3&#xff1a;Snipaste逆天软件系列4&#xff1a;Microsoft To-Do逆天软件系列5&#xff1a;ScreenToGIF逆天软件系列6&#xff1a;Geek Uninstaller逆天软件系列7&#xff1…

C++中绘图工具EasyX基本操作

使用头文件 #include <graphics.h>创建和关闭图形界面 initgraph(800,600); //创建图形界面&#xff0c;参数:图形宽度&#xff0c;图形高度 closegraph(); //关闭图形界面画图形形状 //画圆&#xff0c;参数&#xff1a;圆心点x坐标&#xff0c;圆心点y坐标,半径mm c…

oracle adf_Fn函数来构建Oracle ADF应用程序

oracle adf在我之前的一篇文章中&#xff0c;我描述了如何创建一个Docker容器作为ADF应用程序的构建器。 在这里&#xff0c;我将展示如何将此容器用作 在FN平台的功能 。 首先&#xff0c;让我们更新容器&#xff0c;使其符合功能要求&#xff0c;这意味着可以将其作为接受某…

Sublime Text 4.0 4102 安装插件的问题

文章目录安装包控件&#xff08;Package Control&#xff09;如何安装插件打开 Install Package 面板搜索和安装插件无法打开 Install Package 面板的问题故障排除安装包控件&#xff08;Package Control&#xff09; 要安装插件&#xff0c;必须先安装 Package Control&#…

系统制成docker镜像_docker 制作自己的镜像

查看 docker的服务状态systemctl status docker如果没有启动的话&#xff0c;要先启动 docker 服务systemctl start docker制作自己的镜像1.从远程仓库拉取一个纯净的 centos 系统镜像查询 centos 相关的镜像docker search centos下载镜像到本地docker pull 镜像名查看本地镜像…

Pytorch机器学习/深度学习代码笔记

代码步骤笔记导入模块设置参数数据预处理定义数据集1.Dataset2.ImageFolder加载数据集DataLoadertorchvision--数据预处理要使用的库torchvision.datasetstorchvision.modelstorchvision.transforms训练网络参数训练前的准备设置指定的训练设备(GPU、CPU)定义损失函数定义优化器…

maven安装教程安装教程_Maven教程之春

maven安装教程安装教程1.简介 在这篇文章中&#xff0c;我们将演示如何针对非常特定的用例对Spring使用Maven依赖项。 我们使用的所有库的最新版本都可以在Maven Central上找到。 对于一个有效的构建周期来说&#xff0c;了解Maven依赖项的工作方式以及如何对其进行管理很重要…

如何完全卸载 Sublime Text

只是在应用程序删除软件是不够&#xff0c;你还必须把下面这个目录删除掉才行&#xff1a; /Users/liaowenxiong/Library/Application Support/Sublime Text /Users/liaowenxiong/Library/Preferences/Sublime Text /Users/liaowenxiong/Library/Caches/Sublime Text不这么干&…

5位随机数重复的概率 php_PHP产生不重复随机数的5个方法总结

无论是Web应用&#xff0c;还是WAP或者移动应用&#xff0c;随机数都有其用武之地。在最近接触的几个小项目中&#xff0c;我也经常需要和随机数或者随机数组打交道&#xff0c;所以&#xff0c;对于PHP如何产生不重复随机数常用的几种方法小结一下(ps&#xff1a;方法1、4、5是…

pytorch实现图像分类代码实例

图像多标签分类例子 import os import torch import torch.nn as nn import torchvision.transforms as transforms import torchvision.datasets as datasets import torchvision.models as models import matplotlib.pyplot as plt from matplotlib.ticker import MultipleL…

空调吸气和排气_吸气剂和二传手被认为有害

空调吸气和排气Java程序员习惯性地用“ getters”和“ setters”来修饰类&#xff0c;这种做法根深蒂固&#xff0c;以至于几乎没有人质疑为什么这样做或是否应该这样做。 最近&#xff0c;我认为最好不要这样做&#xff0c;并且我开始在编写的Java代码中避免使用它。 在这篇博…

Sublime Text for Mac 如何格式化代码

文章目录格式化 HTML/CSS/JS格式化 Java/C/C格式化 HTML/CSS/JS 格式化 HTML/CSS/JS&#xff0c;请安装插件&#xff1a;html-css-js prettify 格式化的快捷键&#xff1a;Shift Cmd H html-css-js prettify 的简介&#xff1a; Usage Tools -> Command Palette (CmdS…

ic启动器我的世界_hmcl启动器下载-我的世界HMCL启动器下载 v3.3.172官方最新版--pc6下载站...

我的世界HMCL启动器是我的世界游戏玩家必备的游戏启动器&#xff0c;是三年来超过使用3亿次的老牌启动器&#xff0c;不需要其他任何设置&#xff0c;操作非常方便&#xff0c;本站提供现在最新版本下载。我的世界HMCL启动器是我的世界游戏玩家必备的游戏启动器&#xff0c;是三…

C++核心编程笔记

C核心编程1 内存分区模型1.1 程序运行前1.2 程序运行后1.3 new操作符2 引用2.1 引用的基本使用2.2 引用注意事项2.3 引用做函数参数2.4 引用做函数返回值2.5 引用的本质2.6 常量引用3 函数提高3.1 函数默认参数3.2 函数占位参数3.3 函数重载3.3.1 函数重载概述3.3.2 函数重载注…

Sublime Text 如何设置组合快捷键

Sublime 有个功能叫再次缩进&#xff08;Reindent&#xff09;&#xff0c;我就以这个功能为例讲下如何设置快捷键&#xff0c;这个功能的菜单路径是&#xff1a;Edit ➠ Line ➠ Reindent&#xff0c;有人说这个再次缩进可以格式化代码&#xff0c;扯淡&#xff0c;缩进两下也…

朝着理想坚实迈进_坚实原则:开放/封闭原则

朝着理想坚实迈进先前我们讨论了单一责任原则。 关于实体原则首字母缩写&#xff0c; 打开/关闭原则是该行中的第二个原则。 “软件实体&#xff08;类&#xff0c;模块&#xff0c;功能等&#xff09;应打开以进行扩展&#xff0c;但应关闭以进行修改” 通过采用该原理&…

协程asyncio_Python 异步模块 asyncio 中的协程与任务

协程&#xff08;Coroutine&#xff09;是允许执行被挂起、恢复、以及取消的程序。Python 3 中最初是使用 asyncio.coroutine 装饰器和 yield from 关键字组合来实现协程。单词 yield 在这里并非在生成器&#xff08;Generator&#xff09;中所表示的“产出”&#xff0c;而是交…

ie8兼容性视图灰色修复_IE8网页显示不正常 用”兼容性视图”搞定

网页显示不正常&#xff0c;出现图片错位&#xff0c;文字跑远……等等&#xff0c;别急&#xff0c;试试IE8自带的”兼容性视图”功能吧!其实出现网页显示问题&#xff0c;一般不是您的电脑或者浏览器有问题&#xff0c;而是由于各网站开发标准不同&#xff0c;所以在不同的浏…

GAN对抗生成网络原始论文理解笔记

文章目录论文&#xff1a;Generative Adversarial Nets符号意义生成器(Generator)判别器(Discriminator)生成器和判别器的关系GAN的训练流程简述论文中的生成模型和判别模型GAN的数学理论最大似然估计转换为最小化KL散度问题定义PGP_GPG​全局最优论文&#xff1a;Generative A…

php cdi_CDI和lambda的策略模式

php cdi策略设计模式在运行时动态选择一种实现算法&#xff0c;一种策略。 该模式可用于根据情况选择不同的业务算法。 我们可以将不同的算法实现定义为单独的类。 或者&#xff0c;我们利用Java SE 8 lambda和函数&#xff0c;这些lambda和函数在此处用作轻量级策略实现。 C…