001package org.hl7.fhir.utilities.xml; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import java.io.IOException; 035import java.io.OutputStream; 036import java.io.OutputStreamWriter; 037import java.io.StringWriter; 038import java.io.Writer; 039 040import org.hl7.fhir.exceptions.FHIRException; 041import org.hl7.fhir.utilities.MarkDownProcessor; 042import org.hl7.fhir.utilities.MarkDownProcessor.Dialect; 043import org.hl7.fhir.utilities.Utilities; 044import org.hl7.fhir.utilities.xml.XhtmlGeneratorAdorner.XhtmlGeneratorAdornerState; 045import org.w3c.dom.Comment; 046import org.w3c.dom.DOMException; 047import org.w3c.dom.Document; 048import org.w3c.dom.Element; 049import org.w3c.dom.Node; 050import org.w3c.dom.ProcessingInstruction; 051import org.w3c.dom.Text; 052 053 054public class XhtmlGenerator { 055 056 private static final int LINE_LIMIT = 85; 057 private XhtmlGeneratorAdorner adorner; 058 059 public XhtmlGenerator(XhtmlGeneratorAdorner adorner) { 060 super(); 061 this.adorner = adorner; 062 } 063 064 public String generateInsert(Document doc, String name, String desc) throws Exception { 065 StringWriter out = new StringWriter(); 066 067 out.write("<div class=\"example\">\r\n"); 068 out.write("<p>Example Instance \""+name+"\""+(desc == null ? "" : ": "+Utilities.escapeXml(desc))+"</p>\r\n"); 069 out.write("<pre class=\"xml\">\r\n"); 070 071 XhtmlGeneratorAdornerState state = null; // adorner == null ? new XhtmlGeneratorAdornerState("", "") : adorner.getState(this, null, null); 072 for (int i = 0; i < doc.getChildNodes().getLength(); i++) 073 writeNode(out, doc.getChildNodes().item(i), state, 0); 074 075 out.write("</pre>\r\n"); 076 out.write("</div>\r\n"); 077 out.flush(); 078 return out.toString(); 079 } 080 081 public void generate(Document doc, OutputStream xhtml, String name, String desc, int level, boolean adorn, String filename) throws Exception { 082 adorn = true; // till the xml trick is working 083 084 OutputStreamWriter out = new OutputStreamWriter(xhtml, "UTF-8"); 085 086 out.write("<div class=\"example\">\r\n"); 087 out.write("<p>"+Utilities.escapeXml(desc)+"</p>\r\n"); 088 if (adorn) { 089 out.write("<pre class=\"xml\">\r\n"); 090 out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"); 091 out.write("\r\n"); 092 093 XhtmlGeneratorAdornerState state = null; // adorner == null ? new XhtmlGeneratorAdornerState("", "") : adorner.getState(this, null, null); 094 for (int i = 0; i < doc.getChildNodes().getLength(); i++) 095 writeNode(out, doc.getChildNodes().item(i), state, level); 096 out.write("</pre>\r\n"); 097 } else { 098 out.write("<code class=\"xml\">\r\n"); 099 for (int i = 0; i < doc.getChildNodes().getLength(); i++) 100 writeNodePlain(out, doc.getChildNodes().item(i), level); 101 102 out.write("</code>\r\n"); 103 } 104 out.write("</div>\r\n"); 105 out.flush(); 106 } 107 108 private void writeNodePlain(Writer out, Node node, int level) throws FHIRException, DOMException, IOException { 109 if (node.getNodeType() == Node.ELEMENT_NODE) 110 writeElementPlain(out, (Element) node, level); 111 else if (node.getNodeType() == Node.TEXT_NODE) 112 writeTextPlain(out, (Text) node, level); 113 else if (node.getNodeType() == Node.COMMENT_NODE) 114 writeCommentPlain(out, (Comment) node, level); 115 else if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) 116 writeProcessingInstructionPlain(out, (ProcessingInstruction) node); 117 else if (node.getNodeType() != Node.ATTRIBUTE_NODE) 118 throw new FHIRException("Unhandled node type"); 119 } 120 121 private void writeNode(Writer out, Node node, XhtmlGeneratorAdornerState state, int level) throws Exception { 122 if (node.getNodeType() == Node.ELEMENT_NODE) 123 writeElement(out, (Element) node, state, level); 124 else if (node.getNodeType() == Node.TEXT_NODE) 125 writeText(out, (Text) node, level); 126 else if (node.getNodeType() == Node.COMMENT_NODE) 127 writeComment(out, (Comment) node, level); 128 else if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) 129 writeProcessingInstruction(out, (ProcessingInstruction) node); 130 else if (node.getNodeType() != Node.ATTRIBUTE_NODE) 131 throw new FHIRException("Unhandled node type"); 132 } 133 134 private void writeProcessingInstruction(Writer out, ProcessingInstruction node) { 135 136 137 } 138 139 private void writeProcessingInstructionPlain(Writer out, ProcessingInstruction node) { 140 141 142 } 143 144 private void writeComment(Writer out, Comment node, int level) throws DOMException, IOException { 145 String cmt = node.getTextContent(); 146 if (cmt.contains(":md:")) 147 cmt = processMarkdown(cmt.replace(":md:", "")); 148 else 149 cmt = escapeHtml(cmt, level); 150 out.write("<span class=\"xmlcomment\"><!-- "+cmt+" --></span>"); 151 } 152 153 private void writeCommentPlain(Writer out, Comment node, int level) throws DOMException, IOException { 154 String cmt = node.getTextContent(); 155 if (cmt.contains(":md:")) 156 cmt = processMarkdown(cmt.replace(":md:", "")); 157 else 158 cmt = Utilities.escapeXml(cmt); 159 out.write("<!-- "+cmt+" -->"); 160 } 161 162 private String processMarkdown(String text) { 163 return new MarkDownProcessor(Dialect.COMMON_MARK).process(text, "Xhtml generator"); 164 } 165 166 private void writeText(Writer out, Text node, int level) throws DOMException, IOException { 167 out.write("<b>"+escapeHtml(Utilities.escapeXml(node.getTextContent()), level)+"</b>"); 168 } 169 170 private void writeTextPlain(Writer out, Text node, int level) throws DOMException, IOException { 171 out.write(Utilities.escapeXml(node.getTextContent())); 172 } 173 174 private void writeElement(Writer out, Element node, XhtmlGeneratorAdornerState state, int level) throws Exception { 175 String link = adorner == null ? null : adorner.getLink(this, state, node); 176 if (link != null) 177 out.write("<span class=\"xmltag\"><<a href=\""+link+"\" class=\"xmltag\">"+node.getNodeName()+"</a></span>"); 178 else 179 out.write("<span class=\"xmltagred\"><"+node.getNodeName()+"</span>"); 180 if (node.hasAttributes()) { 181 out.write("<span class=\"xmlattr\">"); 182 out.write("<a name=\""+adorner.getNodeId(state, node)+"\"> </a>"); 183 184 XhtmlGeneratorAdornerState newstate = adorner == null ? new XhtmlGeneratorAdornerState(null, "", "") : adorner.getState(this, state, node); 185 for (int i = 0; i < node.getAttributes().getLength(); i++) { 186 if (i > 0) 187 out.write(" "); 188 if (adorner != null) { 189 XhtmlGeneratorAdornerState attrState = adorner.getAttributeMarkup(this, newstate, node, node.getAttributes().item(i).getNodeName(), node.getAttributes().item(i).getTextContent()); 190 out.write(node.getAttributes().item(i).getNodeName()+"=\"<span class=\"xmlattrvalue\">"+attrState.getPrefix()+escapeHtml(Utilities.escapeXml(node.getAttributes().item(i).getTextContent()), level)+attrState.getSuffix()+"</span>\""); 191 } else 192 out.write(node.getAttributes().item(i).getNodeName()+"=\"<span class=\"xmlattrvalue\">"+escapeHtml(Utilities.escapeXml(node.getAttributes().item(i).getTextContent()), level)+"</span>\""); 193 } 194 out.write("</span>"); 195 196 } 197 if (node.hasChildNodes()) { 198 out.write("<span class=\"xmltag\">></span>"); 199 if (!node.hasAttributes()) 200 out.write("<a name=\""+adorner.getNodeId(state, node)+"\"> </a>"); 201 XhtmlGeneratorAdornerState newstate = adorner == null ? new XhtmlGeneratorAdornerState(null, "", "") : adorner.getState(this, state, node); 202 if (newstate.isSuppress()) 203 out.write("<span class=\"xmlcomment\"><!-- "+escapeHtml(newstate.getSupressionMessage(), level)+" --></span>"); 204 else { 205 out.write(newstate.getPrefix()); 206 for (int i = 0; i < node.getChildNodes().getLength(); i++) 207 writeNode(out, node.getChildNodes().item(i), newstate, level+2); 208 209 out.write(newstate.getSuffix()); 210 } 211 if (link != null) 212 out.write("<span class=\"xmltag\"></<a href=\""+link+"\" class=\"xmltag\">"+node.getNodeName()+"</a>></span>"); 213 else 214 out.write("<span class=\"xmltag\"></"+node.getNodeName()+"></span>"); 215 } 216 else { 217 out.write("<span class=\"xmltag\">/></span>"); 218 if (!node.hasAttributes()) 219 out.write("<a name=\""+adorner.getNodeId(state, node)+"\"> </a>"); 220 } 221 out.write("<a name=\""+adorner.getNodeId(state, node)+"-end\"> </a>"); 222 } 223 224 private void writeElementPlain(Writer out, Element node, int level) throws IOException, FHIRException { 225 out.write("<"+node.getNodeName()); 226 if (node.hasAttributes()) { 227 for (int i = 0; i < node.getAttributes().getLength(); i++) { 228 out.write(" "+node.getAttributes().item(i).getNodeName()+"=\""+Utilities.escapeXml(node.getAttributes().item(i).getTextContent())+"\""); 229 } 230 } 231 if (node.hasChildNodes()) { 232 out.write(">"); 233 for (int i = 0; i < node.getChildNodes().getLength(); i++) 234 writeNodePlain(out, node.getChildNodes().item(i), level+2); 235 out.write("</"+node.getNodeName()+">"); 236 } 237 else 238 out.write("/>"); 239 } 240 241 private String escapeHtml(String doco, int indent) { 242 if (doco == null) 243 return ""; 244 245 int i = 0; 246 StringBuilder b = new StringBuilder(); 247 for (char c : doco.toCharArray()) { 248 i++; 249 if (c == '\r' || c == '\n') 250 i = 0; 251 if ((i > LINE_LIMIT && c == ' ') || (i > LINE_LIMIT + 15)) { 252 b.append("\r\n"); 253 for (int j = 0; j < indent; j++) 254 b.append(" "); 255 i = 0; 256 } 257 if (c == '<') 258 b.append("<"); 259 else if (c == '>') 260 b.append(">"); 261 else if (c == '&') 262 b.append("&"); 263 else if (c == '\t') 264 b.append(" "); 265 else if (c == '\'') 266 b.append("'"); 267 else if (c == '"') 268 b.append("""); 269 else 270 b.append(c); 271 } 272 return b.toString(); 273 } 274}