apache poi_将HTML转换为Apache POI的RichTextString
apache poi
1.概述
在本教程中,我們將構(gòu)建一個(gè)將HTML作為輸入的應(yīng)用程序,并使用提供HTML的RichText表示形式創(chuàng)建Microsoft Excel工作簿。 為了生成Microsoft Excel工作簿,我們將使用Apache POI 。 為了分析HTML,我們將使用Jericho。
Github上提供了本教程的完整源代碼。
2.什么是耶利哥?
Jericho是一個(gè)Java庫(kù) ,它允許對(duì)HTML文檔的各個(gè)部分(包括服務(wù)器端標(biāo)簽)進(jìn)行分析和操作,同時(shí)逐字再現(xiàn)任何無(wú)法識(shí)別或無(wú)效HTML。 它還提供了高級(jí)HTML表單操作功能。 它是一個(gè)開(kāi)放源代碼庫(kù),使用以下許可證發(fā)行: Eclipse公共許可證(EPL) , GNU通用公共許可證(LGPL)和Apache許可證 。
我發(fā)現(xiàn)Jericho非常易于使用,可以實(shí)現(xiàn)將HTML轉(zhuǎn)換為RichText的目標(biāo)。
3. pom.xml
這是我們正在構(gòu)建的應(yīng)用程序所需的依賴項(xiàng)。 請(qǐng)注意,對(duì)于此應(yīng)用程序,我們必須使用Java 9 。 這是因?yàn)槲覀兪褂玫膉ava.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.網(wǎng)頁(yè)– Thymeleaf
我們使用Thymeleaf來(lái)創(chuàng)建一個(gè)基本頁(yè)面,該頁(yè)面具有帶有文本區(qū)域的表單。 Github上提供 Thymeleaf頁(yè)面的源代碼。 如果愿意,可以使用RichText編輯器替換此textarea,例如CKEditor。 我們只需要注意使用適當(dāng)?shù)膕etData方法使AJAX的數(shù)據(jù)正確即可。 在Spring Boot中,以前有一個(gè)關(guān)于CKeditor的教程,標(biāo)題為CKEditor,名為AJAX 。
5.控制器
在我們的控制器中,我們將自動(dòng)裝配JobLauncher和一個(gè)Spring Batch作業(yè),我們將創(chuàng)建一個(gè)名為GenerateExcel的作業(yè) 。 通過(guò)自動(dòng)裝配這兩個(gè)類,當(dāng)POST請(qǐng)求發(fā)送到“ / export”時(shí),我們可以按需運(yùn)行Spring Batch Job GenerateExcel 。
要注意的另一件事是,為了確保Spring Batch作業(yè)將運(yùn)行一次以上,我們?cè)诖舜a中包含唯一參數(shù): addLong(“ uniqueness”,System.nanoTime())。toJobParameters() 。 如果我們不包括唯一參數(shù),則可能會(huì)發(fā)生錯(cuò)誤,因?yàn)橹荒軇?chuàng)建和執(zhí)行唯一的JobInstances,否則Spring Batch無(wú)法區(qū)分第一個(gè)JobInstance和第二個(gè)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.批處理作業(yè)
在批處理作業(yè)的步驟1中,我們調(diào)用getCurrentContent()方法來(lái)獲取傳遞到Thymeleaf表單中的內(nèi)容,創(chuàng)建一個(gè)新的XSSFWorkbook,指定一個(gè)任意的Microsoft Excel Sheet選項(xiàng)卡名稱,然后將所有三個(gè)變量都傳遞到createWorksheet方法中我們將在本教程的下一步中進(jìn)行以下操作:
@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轉(zhuǎn)換為JSON + Spring Batch和Spring Batch CSV Processing 。
7. Excel創(chuàng)建服務(wù)
我們使用各種類來(lái)創(chuàng)建我們的Microsoft Excel文件。 在將HTML轉(zhuǎn)換為RichText時(shí),順序很重要,因此這將是重點(diǎn)。
7.1 RichTextDetails
一個(gè)帶有兩個(gè)參數(shù)的類:一個(gè)字符串,其內(nèi)容將成為RichText,一個(gè)字體映射。
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
一個(gè)POJO,它將跟蹤RichText的位置以及其他內(nèi)容:
public class RichTextInfo {private int startIndex;private int endIndex;private STYLES fontStyle;private String fontValue;// standard getters and setters, and the like7.3樣式
一個(gè)包含要處理HTML標(biāo)記的枚舉。 我們可以根據(jù)需要添加以下內(nèi)容:
public enum STYLES {BOLD("b"), EM("em"), STRONG("strong"), COLOR("color"), UNDERLINE("u"), SPAN("span"), ITALLICS("i"), UNKNOWN("unknown"),PRE("pre");// standard getters and setters7.4 TagInfo
POJO跟蹤標(biāo)簽信息:
public class TagInfo {private String tagName;private String style;private int tagType;// standard getters and setters7.5 HTML為RichText
這不是一個(gè)小類,所以讓我們按方法將其分解。
本質(zhì)上,我們用div標(biāo)簽將任意HTML包圍起來(lái),因此我們知道我們?cè)趯ふ沂裁础?然后,我們?cè)赿iv標(biāo)簽中查找所有元素,將每個(gè)元素添加到RichTextDetails的ArrayList中,然后將整個(gè)ArrayList傳遞給mergeTextDetails方法。 mergeTextDetails返回RichtextString,這是我們需要設(shè)置單元格值的內(nèi)容:
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;} 如上所述,我們?cè)诖朔椒ㄖ袀鬟f了RichTextDetails的ArrayList。 Jericho的設(shè)置采用布爾值來(lái)識(shí)別空標(biāo)簽元素,例如
:已識(shí)別Config.IsHTMLEmptyElementTag。 在與在線富文本編輯器打交道時(shí),這可能很重要,因此我們將其設(shè)置為true。 因?yàn)槲覀冃枰櫾氐捻樞?#xff0c;所以我們使用LinkedHashMap而不是HashMap。
如上所述,我們使用Java 9來(lái)將StringBuilder與java.util.regex.Matcher.appendReplacement結(jié)合使用 。 為什么? 那是因?yàn)镾tringBuffer的運(yùn)行速度比StringBuilder慢。 StringBuffer函數(shù)被同步以確保線程安全,因此速度較慢。
我們使用Deque而不是Stack,因?yàn)镈eque接口提供了更完整和一致的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;}我們?cè)谀睦锸褂肧TYLES枚舉:
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類來(lái)跟蹤當(dāng)前標(biāo)簽:
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標(biāo)簽:
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創(chuàng)建工作表
使用StringBuilder,我創(chuàng)建了一個(gè)要寫(xiě)入FileOutPutStream的字符串。 在實(shí)際應(yīng)用中,應(yīng)由用戶定義。 我在兩個(gè)不同的行上附加了文件夾路徑和文件名。 請(qǐng)將文件路徑更改為您自己的文件路徑。
sheet.createRow(0)在第一行創(chuàng)建一行,而dataRow.createCell(0)在該行的列A中創(chuàng)建一個(gè)單元格。
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.演示
我們?cè)L問(wèn)localhost:8080 。
我們用一些HTML輸入一些文本:
我們打開(kāi)excel文件,然后看到我們創(chuàng)建的RichText:
9.結(jié)論
我們可以看到將HTML轉(zhuǎn)換為Apache POI的RichTextString類并不是一件容易的事。 但是,對(duì)于商業(yè)應(yīng)用程序而言,將HTML轉(zhuǎn)換為RichTextString至關(guān)重要,因?yàn)樵贛icrosoft Excel文件中,可讀性很重要。 我們構(gòu)建的應(yīng)用程序的性能可能還有改進(jìn)的余地,但我們涵蓋了構(gòu)建此類應(yīng)用程序的基礎(chǔ)。
完整的源代碼可在Github上找到。
翻譯自: https://www.javacodegeeks.com/2018/01/converting-html-richtextstring-apache-poi.html
apache poi
總結(jié)
以上是生活随笔為你收集整理的apache poi_将HTML转换为Apache POI的RichTextString的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 上汽名爵 MG Cyberster 纯电
- 下一篇: web前端面试问答_Web服务面试问答