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