package org.lttpp.eemory.enml; import static org.lttpp.eemory.Constants.ENML_ATTR_COLOR; import static org.lttpp.eemory.Constants.ENML_ATTR_FACE; import static org.lttpp.eemory.Constants.ENML_ATTR_FONT; import static org.lttpp.eemory.Constants.ENML_ATTR_HASH; import static org.lttpp.eemory.Constants.ENML_ATTR_STYLE; import static org.lttpp.eemory.Constants.ENML_ATTR_TYPE; import static org.lttpp.eemory.Constants.ENML_DOCTYPE_DECLARATION_SYSTEM_ID; import static org.lttpp.eemory.Constants.ENML_DTD; import static org.lttpp.eemory.Constants.ENML_DTD_LOCATION; import static org.lttpp.eemory.Constants.ENML_TAG_BOLD; import static org.lttpp.eemory.Constants.ENML_TAG_BR; import static org.lttpp.eemory.Constants.ENML_TAG_DIV; import static org.lttpp.eemory.Constants.ENML_TAG_EN_MEDIA; import static org.lttpp.eemory.Constants.ENML_TAG_EN_NOTE; import static org.lttpp.eemory.Constants.ENML_TAG_EN_NOTE_SELF_CLOSING_REGEX; import static org.lttpp.eemory.Constants.ENML_TAG_EN_NOTE_SELF_CLOSING_REPLACEMENT; import static org.lttpp.eemory.Constants.ENML_TAG_EN_NOTE_START_REGEX; import static org.lttpp.eemory.Constants.ENML_TAG_EN_NOTE_START_REPLACEMENT_P1; import static org.lttpp.eemory.Constants.ENML_TAG_ITALIC; import static org.lttpp.eemory.Constants.ENML_TAG_SPAN; import static org.lttpp.eemory.Constants.ENML_VALUE_FONT_SIZE; import static org.lttpp.eemory.Constants.ENML_VALUE_PT; import static org.lttpp.eemory.Constants.XHTML_1_0_LATIN_1_ENT; import static org.lttpp.eemory.Constants.XHTML_1_0_LATIN_1_ENT_LOCATION; import static org.lttpp.eemory.Constants.XHTML_1_0_SPECIAL_ENT; import static org.lttpp.eemory.Constants.XHTML_1_0_SPECIAL_ENT_LOCATION; import static org.lttpp.eemory.Constants.XHTML_1_0_SYMBOL_ENT; import static org.lttpp.eemory.Constants.XHTML_1_0_SYMBOL_ENT_LOCATION; import static org.lttpp.eemory.Constants.XML_VERSION_1_0; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.List; import java.util.regex.Matcher; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.apache.commons.lang3.CharEncoding; import org.apache.commons.lang3.StringUtils; import org.lttpp.eemory.Constants; import org.lttpp.eemory.dom.DOMException; import org.lttpp.eemory.dom.Document; import org.lttpp.eemory.dom.Element; import org.lttpp.eemory.dom.Node; import org.lttpp.eemory.util.ConstantsUtil; import org.lttpp.eemory.util.DomUtil; import org.lttpp.eemory.util.ListUtil; import org.lttpp.eemory.util.LogUtil; import org.lttpp.eemory.util.StringEscapeUtil; import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; public class ENML { // used to create new note private final Document document; private Node root; // used for updating existing note private String existingEnml = StringUtils.EMPTY; private final List<Node> newAddedNodes; private int tabWidth; public ENML() throws DOMException, ParserConfigurationException { document = DomUtil.getBuilder().newDocument(); document.setXmlStandalone(true); document.setXMLEncoding(CharEncoding.UTF_8); document.setXmlVersion(XML_VERSION_1_0); document.appendChild(document.createDocumentType(ENML_TAG_EN_NOTE, null, ENML_DOCTYPE_DECLARATION_SYSTEM_ID)); root = document.createElement(ENML_TAG_EN_NOTE); document.appendChild(root); newAddedNodes = ListUtil.list(); tabWidth = Constants.TAB_WIDTH; } public ENML(final String enml) { document = DomUtil.getBuilder().newDocument(); existingEnml = enml; newAddedNodes = ListUtil.list(); tabWidth = Constants.TAB_WIDTH; } public int getTabWidth() { return tabWidth; } public void setTabWidth(final int tabWidth) { this.tabWidth = tabWidth; } public void addResource(final String hashHex, final String mimeType) throws DOMException, ParserConfigurationException { if (StringUtils.isNotBlank(hashHex)) { Element div = div(); div.appendChild(media(hashHex, mimeType)); newAddedNodes.add(div); } } public void addComment(final String comments) throws DOMException, ParserConfigurationException { if (StringUtils.isNotBlank(comments)) { Element div = div(); div.setTextContent(StringEscapeUtil.escapeEnml(comments) + ConstantsUtil.COLON); newAddedNodes.add(div); } } public void addContent(final List<List<StyleText>> content) throws DOMException, ParserConfigurationException { List<Node> list = parseStyleText(content); newAddedNodes.addAll(list); } /** * Get the string representation of ENML. * * @return the string representation of ENML * @throws ParserConfigurationException * @throws SAXException * @throws IOException */ public String get() throws ParserConfigurationException, SAXException, IOException { if (isCreateNew()) { for (Node n : newAddedNodes) { root.appendChild(n); } String newEnml = DomUtil.toString(document); validateENML(newEnml); LogUtil.debug(newEnml); return newEnml; } else { if (!ListUtil.isNullOrEmptyList(newAddedNodes)) { Element div = div(); div.appendChild(br()); newAddedNodes.add(div); } // open self-closing en-note tag <en-note ... /> to <en-note ... ></en-note> for following processing existingEnml = StringUtils.replacePattern(existingEnml, ENML_TAG_EN_NOTE_SELF_CLOSING_REGEX, ENML_TAG_EN_NOTE_SELF_CLOSING_REPLACEMENT); // insert new nodes existingEnml = StringUtils.replacePattern(existingEnml, ENML_TAG_EN_NOTE_START_REGEX, ENML_TAG_EN_NOTE_START_REPLACEMENT_P1 + Matcher.quoteReplacement(DomUtil.toString(newAddedNodes))); validateENML(existingEnml); LogUtil.debug(existingEnml); return existingEnml; } } private boolean isCreateNew() { return document != null && root != null && StringUtils.isBlank(existingEnml); } private Element div() throws DOMException, ParserConfigurationException { return document.createElement(ENML_TAG_DIV); } private Element br() { return document.createElement(ENML_TAG_BR); } private Element media(final String hashHex, final String mimeType) throws DOMException, ParserConfigurationException { Element media = document.createElement(ENML_TAG_EN_MEDIA); //media.setAttribute(ENML_ATTR_ALIGN, Alignment.LEFT.toString()); // should not have this which break display in browser media.setAttribute(ENML_ATTR_TYPE, mimeType); media.setAttribute(ENML_ATTR_HASH, hashHex); return media; } private List<Node> parseStyleText(final List<List<StyleText>> styleTextBlocks) throws DOMException, ParserConfigurationException { List<Node> list = ListUtil.list(); for (List<StyleText> lineBlocks : styleTextBlocks) { list.add(div(lineBlocks)); } return list; } private Node div(final List<StyleText> styleTextBlocks) throws DOMException, ParserConfigurationException { Element div = document.createElement(ENML_TAG_DIV); for (StyleText styletext : styleTextBlocks) { String escapedXml = StringEscapeUtil.escapeEnml(styletext.getText(), tabWidth); div.appendChild(font(escapedXml, styletext.getFace(), styletext.getColorHexCode(), styletext.getSize(), styletext.getFontStyle())); } return div; } private Node font(final String text, final String face, final String color, final String size, final FontStyle fontStyle) throws DOMException, ParserConfigurationException { Element font = document.createElement(ENML_ATTR_FONT); font.appendChild(span(text, size, fontStyle)); font.setAttribute(ENML_ATTR_FACE, face); font.setAttribute(ENML_ATTR_COLOR, color); //font.setAttribute(ENML_ATTR_SIZE, String.valueOf(2));// does not work if style="font-size:xxpt" of span set return font; } private Node span(final String text, final String size, final FontStyle fontStyle) throws DOMException, ParserConfigurationException { Element span = document.createElement(ENML_TAG_SPAN); if (StringUtils.isEmpty(text)) { span.appendChild(br()); } else { if (fontStyle == FontStyle.BOLD) { span.appendChild(b(text)); } else if (fontStyle == FontStyle.ITALIC) { span.appendChild(i(text)); } else if (fontStyle == FontStyle.NORMAL) { span.appendChild(text(text)); } else if (fontStyle == FontStyle.BOLD_ITALIC) { span.appendChild(bi(text)); } } span.setAttribute(ENML_ATTR_STYLE, ENML_VALUE_FONT_SIZE + size + ENML_VALUE_PT); return span; } private Node b(final String text) throws DOMException, ParserConfigurationException { Node b = document.createElement(ENML_TAG_BOLD); b.setTextContent(text); return b; } private Node i(final String text) throws DOMException, ParserConfigurationException { Node i = document.createElement(ENML_TAG_ITALIC); i.setTextContent(text); return i; } private Node bi(final String text) throws DOMException, ParserConfigurationException { Node i = document.createElement(ENML_TAG_ITALIC); i.setTextContent(text); Node b = document.createElement(ENML_TAG_BOLD); b.appendChild(i); return b; } private Node text(final String text) throws DOMException, ParserConfigurationException { return document.createTextNode(text); } /** * Validate ENML string. * * @param enml * ENML string to be validated * @throws ParserConfigurationException * @throws SAXException * @throws IOException */ public static void validateENML(final String enml) throws ParserConfigurationException, SAXException, IOException { SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setValidating(true); SAXParser parser = factory.newSAXParser(); XMLReader reader = parser.getXMLReader(); reader.setEntityResolver(new EntityResolver() { @Override public InputSource resolveEntity(final String publicId, final String systemId) throws SAXException, IOException { if (systemId.endsWith(ENML_DTD)) { return new InputSource(getClass().getResourceAsStream(ENML_DTD_LOCATION)); } else if (systemId.endsWith(XHTML_1_0_LATIN_1_ENT)) { return new InputSource(getClass().getResourceAsStream(XHTML_1_0_LATIN_1_ENT_LOCATION)); } else if (systemId.endsWith(XHTML_1_0_SYMBOL_ENT)) { return new InputSource(getClass().getResourceAsStream(XHTML_1_0_SYMBOL_ENT_LOCATION)); } else if (systemId.endsWith(XHTML_1_0_SPECIAL_ENT)) { return new InputSource(getClass().getResourceAsStream(XHTML_1_0_SPECIAL_ENT_LOCATION)); } else { return null; } } }); reader.setErrorHandler(new ErrorHandler() { @Override public void warning(final SAXParseException exception) throws SAXException { LogUtil.logWarning(enml); throw exception; } @Override public void fatalError(final SAXParseException exception) throws SAXException { LogUtil.logError(enml); throw exception; } @Override public void error(final SAXParseException exception) throws SAXException { LogUtil.logError(enml); throw exception; } }); reader.parse(new InputSource(new ByteArrayInputStream(enml.getBytes(CharEncoding.UTF_8)))); } }