将HTML转换为Apache POI的RichTextString
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處理 。
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 like7.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 setters7.4 TagInfo
POJO跟蹤標簽信息:
public class TagInfo {private String tagName;private String style;private int tagType;// standard getters and setters7.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。 
如上所述,我們使用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
總結
以上是生活随笔為你收集整理的将HTML转换为Apache POI的RichTextString的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 安卓看电池损耗(安卓看电池)
- 下一篇: Fn函数来构建Oracle ADF应用程
