/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.dg.docx; import java.awt.Color; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.logging.Logger; import javax.swing.ImageIcon; import javax.swing.text.AttributeSet; import javax.swing.text.MutableAttributeSet; import javax.swing.text.html.CSS; import javax.swing.text.html.HTML; import javax.swing.text.html.HTML.Attribute; import javax.swing.text.html.HTML.Tag; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.parser.ParserDelegator; import org.apache.commons.lang.StringEscapeUtils; import org.openflexo.logging.FlexoLogger; import org.openflexo.toolbox.HTMLUtils; import org.openflexo.toolbox.StringUtils; import org.openflexo.xmlcode.XMLCoder; public class DocxVelocityXmlFromHtml { protected static final Logger logger = FlexoLogger.getLogger(DocxVelocityXmlFromHtml.class.getPackage().getName()); private String docxXmlString; private Set<String> additionalRelationships; private List<String> orderedBulletsIds; private List<String> bulletsIds; private int currentUniqueId; public DocxVelocityXmlFromHtml(String htmlString, int currentUniqueId, boolean isWithinP, ProjectDocDocxGenerator projectDocxGenerator) { if (htmlString != null) { Html2DocxVelocityXmlParserCallback callback = new Html2DocxVelocityXmlParserCallback(currentUniqueId, isWithinP, projectDocxGenerator); Reader reader = new StringReader(htmlString); try { new ParserDelegator().parse(reader, callback, false); } catch (IOException e1) { e1.printStackTrace(); } this.docxXmlString = callback.getBuildedString(); this.additionalRelationships = callback.getAdditionalRelationships(); this.orderedBulletsIds = callback.getOrderedBulletsIds(); this.bulletsIds = callback.getBulletsIds(); this.currentUniqueId = callback.getCurrentUniqueId(); } else { this.currentUniqueId = currentUniqueId; } } public String getDocxXmlString() { return docxXmlString; } public Set<String> getAdditionalRelationships() { return additionalRelationships; } public List<String> getOrderedBulletsIds() { return orderedBulletsIds; } public List<String> getBulletsIds() { return bulletsIds; } public int getCurrentUniqueId() { return currentUniqueId; } private static class Html2DocxVelocityXmlParserCallback extends HTMLEditorKit.ParserCallback { private boolean withinHead = false; private boolean withinParagraph = false; private boolean withinR = false; private boolean isWithinA = false; private int currentUniqueId; private HTMLToDocxPropertiesStack rProperties = new HTMLToDocxPropertiesStack(false); private HTMLToDocxPropertiesStack pProperties = new HTMLToDocxPropertiesStack(true); private StringBuilder sb = new StringBuilder(); private Set<String> additionalRelationships = new HashSet<String>(); private int currentNumberingId = 0; private Integer currentNumberingLevel = null; private List<String> orderedBulletsIds = new ArrayList<String>(); private List<String> bulletsIds = new ArrayList<String>(); private boolean needCloseParagraph; private ProjectDocDocxGenerator projectDocxGenerator; private boolean withinT = false; private Map<Integer, Integer> rowSpanToDistribute; private int currentColumn = -1; private String align; public Html2DocxVelocityXmlParserCallback(int currentUniqueId, boolean isWithinP, ProjectDocDocxGenerator projectDocxGenerator) { super(); this.needCloseParagraph = !isWithinP; this.withinParagraph = isWithinP; this.currentUniqueId = currentUniqueId; this.projectDocxGenerator = projectDocxGenerator; } @Override public void handleText(char[] data, int pos) { ensureIsWithinParagraph(); ensureIsWithinR(); if (!withinT) { sb.append(StringUtils.LINE_SEPARATOR).append("<w:t xml:space=\"preserve\">"); } sb.append(StringEscapeUtils.escapeXml(XMLCoder.removeUnacceptableChars(new String(data)))); sb.append("</w:t>"); sb.append(StringUtils.LINE_SEPARATOR).append("</w:r>"); withinT = false; withinR = false; } @Override public void handleComment(char[] data, int pos) { } @Override public void handleSimpleTag(Tag t, MutableAttributeSet a, int pos) { if (t == HTML.Tag.IMG) { handleImage(a); } else if (t == HTML.Tag.BR) { handleBR(); } } @Override public void handleStartTag(Tag t, MutableAttributeSet a, int pos) { if (t == HTML.Tag.HTML || t == HTML.Tag.BODY) { return; } if (t == HTML.Tag.HEAD) { withinHead = true; return; } if (withinHead) { return; } if (t == HTML.Tag.P || t == HTML.Tag.DIV) { handleStartParagraph(a); } else if (t == HTML.Tag.H1 || t == HTML.Tag.H2 || t == HTML.Tag.H3 || t == HTML.Tag.H4 || t == HTML.Tag.H5 || t == HTML.Tag.H6) { handleStartHeading(t, a); } else if (t == HTML.Tag.A) { handleStartAnchor(a); } else if (t == HTML.Tag.B) { handleStartBold(a); } else if (t == HTML.Tag.I) { handleStartItalic(a); } else if (t == HTML.Tag.U) { handleStartUnderline(a); } else if (t == HTML.Tag.FONT) { handleStartFont(a); } else if (t == HTML.Tag.UL || t == HTML.Tag.OL) { handleStartList(t, a); } else if (t == HTML.Tag.LI) { handleStartListItem(a); } else if (t == HTML.Tag.SPAN) { handleStartSpan(a); } else if (t == HTML.Tag.TABLE) { handleStartTable(a); } else if (t == HTML.Tag.TR) { handleStartTableRow(a); } else if (t == HTML.Tag.TD) { handleStartTableCell(a); } else { handleUnknownStartTag(t, pos); } } private void handleStartTable(MutableAttributeSet a) { sb.append("<w:tbl>\n"); sb.append("<w:tblPr>\n"); if (a.getAttribute(HTML.Attribute.TITLE) != null) { sb.append("<w:tblCaption w:val=\"") .append(StringEscapeUtils.escapeXml(XMLCoder.removeUnacceptableChars(a.getAttribute(HTML.Attribute.TITLE) .toString()))).append("\" />"); } sb.append(" <w:tblStyle w:val=\"TableGrid\"/>\n"); sb.append(" <w:tblW w:w=\"0\" w:type=\"auto\"/>\n"); sb.append(" <w:jc w:val=\"center\"/>\n"); sb.append(" <w:tblBorders>\n"); sb.append(" <w:top w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/>\n"); sb.append(" <w:left w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/>\n"); sb.append(" <w:bottom w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/>\n"); sb.append(" <w:right w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/>\n"); sb.append(" <w:insideH w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/>\n"); sb.append(" <w:insideV w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/>\n"); sb.append(" </w:tblBorders>\n"); sb.append(" <w:tblCellMar>\n"); sb.append(" <w:top w:w=\"57\" w:type=\"dxa\"/>\n"); sb.append(" <w:bottom w:w=\"57\" w:type=\"dxa\"/>\n"); sb.append(" </w:tblCellMar>\n"); sb.append(" <w:tblLook w:val=\"04A0\"/>\n"); sb.append(" </w:tblPr>"); } private void handleEndTable() { sb.append("</w:tbl>"); } private void handleStartTableRow(MutableAttributeSet a) { sb.append("<w:tr>\n"); sb.append(" <w:trPr>\n"); sb.append(" <w:cnfStyle w:val=\"100000000000\"/>\n"); sb.append(" <w:trHeight w:val=\"300\"/>\n"); sb.append(" <w:jc w:val=\"center\"/>\n"); sb.append(" <w:cantSplit/>\n"); sb.append(" </w:trPr>"); currentColumn = 0; } private void handleEndTableRow() { sb.append("</w:tr>"); currentColumn = -1; } private void handleStartTableCell(MutableAttributeSet a) { sb.append("<w:tc>\n"); sb.append(" <w:tcPr>\n"); if (a.getAttribute(HTML.Attribute.COLSPAN) != null) { sb.append("<w:gridSpan w:val=\"" + a.getAttribute(HTML.Attribute.COLSPAN) + "\"/>"); } Object rowSpan = a.getAttribute(HTML.Attribute.ROWSPAN); if (rowSpan != null) { sb.append("<w:vMerge w:val=\"restart\" />"); if (rowSpanToDistribute == null) { rowSpanToDistribute = new HashMap<Integer, Integer>(); } rowSpanToDistribute.put(currentColumn, Integer.parseInt(rowSpan.toString())); } else if (rowSpanToDistribute != null && rowSpanToDistribute.get(currentColumn) != null) { int span = rowSpanToDistribute.get(currentColumn); if (span > 1) { sb.append("<w:vMerge/>"); if (span > 2) { rowSpanToDistribute.put(currentColumn, span - 1); } else { rowSpanToDistribute.remove(currentColumn); } } } if (a.getAttribute(HTML.Attribute.ALIGN) != null) { setAlignementFromAlignValue(a.getAttribute(HTML.Attribute.ALIGN).toString()); } if (a.getAttribute(HTML.Attribute.VALIGN) != null) { String valign = a.getAttribute(HTML.Attribute.VALIGN).toString(); if (valign.equalsIgnoreCase("top")) { valign = "top"; } else if (valign.equalsIgnoreCase("bottom")) { valign = "bottom"; } else { valign = "center"; } sb.append("<w:vAlign w:val=\"").append(valign).append("\" />"); } if (a.getAttribute(HTML.Attribute.STYLE) != null) { String style = a.getAttribute(HTML.Attribute.STYLE).toString(); String[] styles = style.split(";"); for (String s : styles) { if (s.startsWith("text-align:")) { int index = s.indexOf(';'); if (index < 0) { index = s.length(); } setAlignementFromAlignValue(s.substring("text-align:".length(), index).trim()); } else if (s.startsWith("background-color")) { int index = s.indexOf(';'); if (index < 0) { index = s.length(); } Color color = HTMLUtils.extractColorFromString(s.substring("background-color:".length(), index)); if (color != null) { sb.append("<w:shd w:val=\"clear\" w:color=\"auto\" w:fill=\"").append(HTMLUtils.toHexString(color)) .append("\" />"); } } } } sb.append(" <w:tcBorders>\n"); sb.append(" <w:bottom w:val=\"none\" w:sz=\"0\" w:space=\"0\" w:color=\"auto\"/>\n"); sb.append(" </w:tcBorders>\n"); sb.append(" </w:tcPr>"); sb.append("<w:p>\n"); sb.append(" <w:pPr><w:keepNext/></w:pPr>\n"); sb.append(" <w:r>\n"); sb.append(" <w:t>"); withinParagraph = true; withinR = true; withinT = true; currentColumn++; } protected void setAlignementFromAlignValue(String value) { if (value.equalsIgnoreCase("justify")) { align = "both"; } else if (value.equalsIgnoreCase("left")) { align = "start"; } else if (value.equalsIgnoreCase("center")) { align = "center"; } else if (value.equalsIgnoreCase("right")) { align = "end"; } else { align = null; } } private void handleEndTableCell() { align = null; closeParagraph(); sb.append("</w:tc>"); } @Override public void handleEndTag(Tag t, int pos) { if (t == HTML.Tag.HEAD) { withinHead = false; return; } if (t == HTML.Tag.P || t == HTML.Tag.DIV || t == HTML.Tag.LI || t == HTML.Tag.H1 || t == HTML.Tag.H2 || t == HTML.Tag.H3 || t == HTML.Tag.H4 || t == HTML.Tag.H5 || t == HTML.Tag.H6) { pProperties.pop(); closeParagraph(); } else if (t == HTML.Tag.A) { rProperties.pop(); if (isWithinA) { sb.append("</w:hyperlink>"); isWithinA = false; } } else if (t == HTML.Tag.B) { rProperties.pop(); } else if (t == HTML.Tag.I) { rProperties.pop(); } else if (t == HTML.Tag.U) { rProperties.pop(); } else if (t == HTML.Tag.FONT) { rProperties.pop(); } else if (t == HTML.Tag.UL || t == HTML.Tag.OL) { pProperties.pop(); currentNumberingLevel--; if (currentNumberingLevel < 0) { currentNumberingLevel = null; } } else if (t == HTML.Tag.SPAN) { rProperties.pop(); } else if (t == HTML.Tag.TABLE) { handleEndTable(); } else if (t == HTML.Tag.TR) { handleEndTableRow(); } else if (t == HTML.Tag.TD) { handleEndTableCell(); } else { handleUnknownEndTag(t, pos); } } private boolean ensureIsWithinParagraph() { return ensureIsWithinParagraph(false); } private boolean ensureIsWithinParagraph(boolean isStartListItem) { if (!withinParagraph) { sb.append(StringUtils.LINE_SEPARATOR).append("<w:p>"); withinParagraph = true; sb.append(getCurrentpPr(isStartListItem)); return true; } return false; } private void closeParagraph() { if (withinParagraph) { if (withinR) { if (withinT) { sb.append("</w:t>"); withinT = false; } else { sb.append(StringUtils.LINE_SEPARATOR).append("<w:t></w:t>"); } sb.append(StringUtils.LINE_SEPARATOR).append("</w:r>"); withinR = false; } else if (sb.toString().endsWith("<w:p>")) { sb.append(StringUtils.LINE_SEPARATOR).append("<w:r>"); sb.append(StringUtils.LINE_SEPARATOR).append("<w:t></w:t>"); sb.append(StringUtils.LINE_SEPARATOR).append("</w:r>"); } sb.append(StringUtils.LINE_SEPARATOR).append("</w:p>"); withinParagraph = false; } } public void ensureIsWithinR() { if (!withinR) { sb.append(StringUtils.LINE_SEPARATOR + "<w:r>"); String rPr = rProperties.getCurrentDocxProperties(); if (rPr.length() > 0) { sb.append(StringUtils.LINE_SEPARATOR).append("<w:rPr>"); sb.append(rPr); sb.append(StringUtils.LINE_SEPARATOR).append("</w:rPr>"); } withinR = true; } } private void handleStartSpan(MutableAttributeSet a) { rProperties.push(HTML.Tag.SPAN, a); } private void handleStartListItem(MutableAttributeSet a) { closeParagraph(); pProperties.push(HTML.Tag.LI, a); ensureIsWithinParagraph(true); } private void handleStartList(Tag tag, MutableAttributeSet a) { pProperties.push(tag, a); if (currentNumberingLevel == null) { currentNumberingId = getUniqueId(); currentNumberingLevel = 0; if (tag == HTML.Tag.UL) { bulletsIds.add(String.valueOf(currentNumberingId)); } else { orderedBulletsIds.add(String.valueOf(currentNumberingId)); } } else { currentNumberingLevel++; } } private void handleImage(MutableAttributeSet a) { String imageSubPath = (String) a.getAttribute(HTML.Attribute.SRC); ImageIcon imageIcon = projectDocxGenerator.getImageIconForImportedImageNamed(imageSubPath); if (imageIcon == null) { return; } String widthAttributeString = (String) a.getAttribute(HTML.Attribute.WIDTH); String heightAttributeString = (String) a.getAttribute(HTML.Attribute.HEIGHT); Integer imageWidth = null; Integer imageHeight = null; if (widthAttributeString != null) { try { imageWidth = Integer.parseInt(widthAttributeString); } catch (NumberFormatException e) { // Not a number, let it null } } if (heightAttributeString != null) { try { imageHeight = Integer.parseInt(heightAttributeString); } catch (NumberFormatException e) { // Not a number, let it null } } double imageRatio = (double) imageIcon.getIconWidth() / (double) imageIcon.getIconHeight(); if (imageWidth == null) { if (imageHeight != null) { imageWidth = new Double(imageHeight * imageRatio).intValue(); } else { imageWidth = imageIcon.getIconWidth(); } } if (imageHeight == null) { imageHeight = new Double(imageWidth / imageRatio).intValue(); } if (imageWidth > 514) { imageHeight = 514 * imageHeight / imageWidth; imageWidth = 514; } if (imageHeight > 734) { imageWidth = 734 * imageWidth / imageHeight; imageHeight = 734; } imageWidth = imageWidth * 9525; imageHeight = imageHeight * 9525; ensureIsWithinParagraph(); ensureIsWithinR(); int uniqueID = getUniqueId(); StringBuilder imageXml = new StringBuilder(); imageXml.append(StringUtils.LINE_SEPARATOR).append("<w:drawing>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <wp:inline distT=\"0\" distB=\"0\" distL=\"0\" distR=\"0\">"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <wp:extent cx=\"").append(imageWidth).append("\" cy=\"") .append(imageHeight).append("\" />"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <wp:docPr id=\"").append(uniqueID).append("\" name=\"Picture\" />"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <wp:cNvGraphicFramePr>"); imageXml.append(StringUtils.LINE_SEPARATOR).append( " <a:graphicFrameLocks xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" noChangeAspect=\"1\" />"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" </wp:cNvGraphicFramePr>"); imageXml.append(StringUtils.LINE_SEPARATOR).append( " <a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">"); imageXml.append(StringUtils.LINE_SEPARATOR).append( " <a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"); imageXml.append(StringUtils.LINE_SEPARATOR).append( " <pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <pic:nvPicPr>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <pic:cNvPr id=\"0\" name=\"\" />"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <pic:cNvPicPr/>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" </pic:nvPicPr>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <pic:blipFill>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <a:blip r:embed=\"") .append(projectDocxGenerator.getRIdForString(imageSubPath)).append("\" />"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <a:stretch>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <a:fillRect/>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" </a:stretch>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" </pic:blipFill>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <pic:spPr>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <a:xfrm>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <a:off x=\"0\" y=\"0\" />"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <a:ext cx=\"").append(imageWidth).append("\" cy=\"") .append(imageHeight).append("\" />"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" </a:xfrm>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <a:prstGeom prst=\"rect\">"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" <a:avLst/>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" </a:prstGeom>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" </pic:spPr>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" </pic:pic>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" </a:graphicData>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" </a:graphic>"); imageXml.append(StringUtils.LINE_SEPARATOR).append(" </wp:inline>"); imageXml.append(StringUtils.LINE_SEPARATOR).append("</w:drawing>"); imageXml.append(StringUtils.LINE_SEPARATOR).append("</w:r>"); sb.append(imageXml); withinR = false; String rel = "<Relationship Id=\"" + projectDocxGenerator.getRIdForString(imageSubPath) + "\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image\" Target=\"" + projectDocxGenerator.getMediaRelativePath(imageSubPath) + "\"/>"; getAdditionalRelationships().add(rel); } private void handleStartFont(AttributeSet attributeSet) { rProperties.push(HTML.Tag.FONT, attributeSet); } private void handleStartUnderline(AttributeSet attributeSet) { rProperties.push(HTML.Tag.U, attributeSet); } private void handleStartItalic(AttributeSet attributeSet) { rProperties.push(HTML.Tag.I, attributeSet); } private void handleStartBold(AttributeSet attributeSet) { rProperties.push(HTML.Tag.B, attributeSet); } private void handleStartAnchor(AttributeSet attributeSet) { String href = (String) attributeSet.getAttribute(HTML.Attribute.HREF); rProperties.push(HTML.Tag.A, attributeSet); if (href == null) { href = (String) attributeSet.getAttribute(HTML.Attribute.NAME); } if (href != null) { href = href.trim(); boolean isAnchor = href.startsWith("#"); String target = (String) attributeSet.getAttribute(HTML.Attribute.TARGET); String title = (String) attributeSet.getAttribute(HTML.Attribute.TITLE); if (href.startsWith("file:/") && !href.startsWith("file:///")) { href = "file:///" + href.substring(6); } if (href.startsWith("\\\\")) { href = "file:///" + href; } href = href.replaceAll(" ", "%20"); ensureIsWithinParagraph(); sb.append("<w:hyperlink"); isWithinA = true; if (isAnchor) { sb.append(" w:anchor=\"").append(href.substring(1)).append("\""); } else { String rId = projectDocxGenerator.getRIdForString(href); sb.append(" r:id=\"").append(rId).append("\""); String rel = "<Relationship Id=\"" + rId + "\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink\" Target=\"" + href + "\" TargetMode=\"External\"/>"; getAdditionalRelationships().add(rel); } if (target != null && target.length() > 0) { sb.append(" w:tgtFrame=\"").append(target).append("\""); } if (title != null && title.length() > 0) { sb.append(" w:tooltip=\"").append(title).append("\""); } sb.append(">"); } } private void handleBR() { closeParagraph(); ensureIsWithinParagraph(); } private void handleStartParagraph(AttributeSet attributeSet) { pProperties.push(HTML.Tag.P, attributeSet); ensureIsWithinParagraph(); } private void handleStartHeading(Tag tag, AttributeSet attributeSet) { pProperties.push(tag, attributeSet); ensureIsWithinParagraph(); } private void handleUnknownStartTag(Tag t, int pos) { } private void handleUnknownEndTag(Tag t, int pos) { } private String getCurrentpPr(boolean isStartListItem) { StringBuilder tmpSb = new StringBuilder(); if (currentNumberingLevel != null) { tmpSb.append(StringUtils.LINE_SEPARATOR).append("<w:pStyle w:val=\"ListParagraph\"/>"); if (isStartListItem) { tmpSb.append(StringUtils.LINE_SEPARATOR).append("<w:numPr>"); tmpSb.append(StringUtils.LINE_SEPARATOR).append("<w:ilvl w:val=\"").append(currentNumberingLevel).append("\"/>"); tmpSb.append(StringUtils.LINE_SEPARATOR).append("<w:numId w:val=\"").append(currentNumberingId).append("\"/>"); tmpSb.append(StringUtils.LINE_SEPARATOR).append("</w:numPr>"); } } tmpSb.append(pProperties.getCurrentDocxProperties()); if (tmpSb.length() > 0) { tmpSb.insert(0, StringUtils.LINE_SEPARATOR + "<w:pPr>"); tmpSb.append(StringUtils.LINE_SEPARATOR).append("</w:pPr>"); } return tmpSb.toString(); } public String getBuildedString() { if (needCloseParagraph) { closeParagraph(); } else { ensureIsWithinParagraph(); } return sb.toString(); } public Set<String> getAdditionalRelationships() { return additionalRelationships; } public List<String> getBulletsIds() { return bulletsIds; } public List<String> getOrderedBulletsIds() { return orderedBulletsIds; } public int getCurrentUniqueId() { return currentUniqueId; } public int getUniqueId() { return currentUniqueId++; } private class HTMLToDocxPropertiesStack { private static final String RSTYLEKEY = "rStyle"; private static final String PSTYLEKEY = "pStyle"; private static final String LINKSTYLEKEY = "linkStyle"; // Specific key because it applies only if no other rStyle has been set private Stack<Map<String, String>> propertiesStack = new Stack<Map<String, String>>(); private boolean paragraphLevel; public HTMLToDocxPropertiesStack(boolean paragraphLevel) { this.paragraphLevel = paragraphLevel; } public void push(HTML.Tag tag, AttributeSet attributeSet) { StringBuilder computedStyle = new StringBuilder(); Map<String, String> map = new HashMap<String, String>(); if (tag == HTML.Tag.B) { computedStyle.append(CSS.Attribute.FONT_WEIGHT + ": bold"); } else if (tag == HTML.Tag.U) { computedStyle.append(CSS.Attribute.TEXT_DECORATION + ": underline"); } else if (tag == HTML.Tag.I) { computedStyle.append(CSS.Attribute.FONT_STYLE + ": italic"); } else if (tag == HTML.Tag.A) { map.put(LINKSTYLEKEY, "<w:rStyle w:val=\"Hyperlink\"/>"); } for (Enumeration<?> en = attributeSet.getAttributeNames(); en.hasMoreElements();) { Object attributeName = en.nextElement(); Object attributeValue = attributeSet.getAttribute(attributeName); if (attributeName == null) { continue; } if (attributeName == Attribute.STYLE) { if (attributeValue != null && attributeValue.toString().trim().length() > 0) { computedStyle.append(attributeValue.toString().trim()); } } else if (attributeName == Attribute.CLASS) { if (attributeValue != null && attributeValue.toString().trim().length() > 0 && !attributeValue.equals("no-style")) { map.put(paragraphLevel ? PSTYLEKEY : RSTYLEKEY, "<w:" + (paragraphLevel ? "p" : "r") + "Style w:val=\"" + attributeValue.toString().trim() + "\"/>"); } } else if (tag == HTML.Tag.FONT) { if (attributeName == Attribute.SIZE) { if (attributeValue != null) { computedStyle.append(CSS.Attribute.FONT_SIZE).append(": ") .append(HTMLUtils.getFontSizeInPointsFromFontValue(attributeValue.toString())).append("pt"); } } else if (attributeName == Attribute.COLOR) { computedStyle.append(CSS.Attribute.COLOR).append(": ").append(attributeValue); } } else if (tag == HTML.Tag.P) { if (attributeName == Attribute.ALIGN) { computedStyle.append(CSS.Attribute.TEXT_ALIGN).append(": ").append(attributeValue); } } if (computedStyle.length() > 0 && !computedStyle.toString().endsWith(";")) { computedStyle.append(';'); } } map.putAll(getMapForStyle(computedStyle.toString())); if (tag == HTML.Tag.H1) { map.put(PSTYLEKEY, "<w:pStyle w:val=\"Heading1\"/>"); } else if (tag == HTML.Tag.H2) { map.put(PSTYLEKEY, "<w:pStyle w:val=\"Heading2\"/>"); } else if (tag == HTML.Tag.H3) { map.put(PSTYLEKEY, "<w:pStyle w:val=\"Heading3\"/>"); } else if (tag == HTML.Tag.H4) { map.put(PSTYLEKEY, "<w:pStyle w:val=\"Heading4\"/>"); } else if (tag == HTML.Tag.H5) { map.put(PSTYLEKEY, "<w:pStyle w:val=\"Heading5\"/>"); } else if (tag == HTML.Tag.H6) { map.put(PSTYLEKEY, "<w:pStyle w:val=\"Heading6\"/>"); } propertiesStack.push(map); } public void pop() { if (propertiesStack.size() > 0) { propertiesStack.pop(); } } public String getCurrentDocxProperties() { Map<String, String> currentProperties = new HashMap<String, String>(); // Merge properties for (Map<String, String> propertiesMap : propertiesStack) { currentProperties.putAll(propertiesMap); } if (align != null) { currentProperties.put(CSS.Attribute.TEXT_ALIGN.toString(), StringUtils.LINE_SEPARATOR + "<w:jc w:val=\"" + align + "\"/>"); } // Hack for link style -> only applies if no rStyle set if (currentProperties.containsKey(LINKSTYLEKEY) && currentProperties.containsKey(RSTYLEKEY)) { currentProperties.remove(LINKSTYLEKEY); } StringBuilder propertiesString = new StringBuilder(); for (String property : currentProperties.values()) { propertiesString.append(StringUtils.LINE_SEPARATOR).append(property); } return propertiesString.toString(); } private Map<String, String> getMapForStyle(String styleValue) { Map<String, String> map = new HashMap<String, String>(); for (String styleEffect : styleValue.split(";")) { styleEffect = styleEffect.trim(); int indexOf = styleEffect.indexOf(':'); if (indexOf == -1 || styleEffect.length() <= indexOf + 1) { continue; } String effectKey = styleEffect.substring(0, indexOf).trim(); String effectValue = styleEffect.substring(indexOf + 1).trim(); CSS.Attribute attribute = CSS.getAttribute(effectKey); if (attribute == null) { continue; } if (attribute == CSS.Attribute.COLOR) { Color color = HTMLUtils.extractColorFromString(effectValue); if (color != null) { map.put(attribute.toString(), StringUtils.LINE_SEPARATOR + "<w:color w:val=\"" + HTMLUtils.toHexString(color) + "\"/>"); } } else if (attribute == CSS.Attribute.BACKGROUND_COLOR) { Color color = HTMLUtils.extractColorFromString(effectValue); if (color != null) { map.put(attribute.toString(), StringUtils.LINE_SEPARATOR + "<w:shd w:val=\"clear\" w:color=\"auto\" w:fill=\"" + HTMLUtils.toHexString(color) + "\" />"); } } else if (attribute == CSS.Attribute.FONT_SIZE) { Integer sizeInPoints = HTMLUtils.getFontSizeInPoints(effectValue); if (sizeInPoints != null) { map.put(attribute.toString(), StringUtils.LINE_SEPARATOR + "<w:sz w:val=\"" + sizeInPoints * 2 + "\"/>" + StringUtils.LINE_SEPARATOR + "<w:szCs w:val=\"" + sizeInPoints * 2 + "\"/>"); } } else if (attribute == CSS.Attribute.FONT_WEIGHT) { if (effectValue.equals("bold")) { map.put(attribute.toString(), StringUtils.LINE_SEPARATOR + "<w:b/>"); } } else if (attribute == CSS.Attribute.TEXT_DECORATION) { if (effectValue.equals("underline")) { map.put(attribute.toString(), StringUtils.LINE_SEPARATOR + "<w:u w:val=\"single\"/>"); } } else if (attribute == CSS.Attribute.FONT_STYLE) { if (effectValue.equals("italic")) { map.put(attribute.toString(), StringUtils.LINE_SEPARATOR + "<w:i/>"); } } else if (attribute == CSS.Attribute.TEXT_ALIGN) { String alignValue = null; if ("left".equals(effectValue)) { alignValue = "left"; } else if ("right".equals(effectValue)) { alignValue = "right"; } else if ("center".equals(effectValue)) { alignValue = "center"; } else if ("justify".equals(effectValue)) { alignValue = "both"; } if (alignValue != null) { map.put(attribute.toString(), StringUtils.LINE_SEPARATOR + "<w:jc w:val=\"" + alignValue + "\"/>"); } } else if (attribute == CSS.Attribute.BACKGROUND) { // Get only the color if any for (String backgroundItem : effectValue.split(" ")) { Color color = HTMLUtils.extractColorFromString(backgroundItem.trim()); if (color != null) { map.put(CSS.Attribute.BACKGROUND_COLOR.toString(), StringUtils.LINE_SEPARATOR + "<w:shd w:val=\"clear\" w:color=\"auto\" w:fill=\"" + HTMLUtils.toHexString(color) + "\" />"); } } } } return map; } } } }