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
031/*-
032 * #%L
033 * org.hl7.fhir.utilities
034 * %%
035 * Copyright (C) 2014 - 2019 Health Level 7
036 * %%
037 * Licensed under the Apache License, Version 2.0 (the "License");
038 * you may not use this file except in compliance with the License.
039 * You may obtain a copy of the License at
040 * 
041 *      http://www.apache.org/licenses/LICENSE-2.0
042 * 
043 * Unless required by applicable law or agreed to in writing, software
044 * distributed under the License is distributed on an "AS IS" BASIS,
045 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
046 * See the License for the specific language governing permissions and
047 * limitations under the License.
048 * #L%
049 */
050
051
052import java.io.IOException;
053import java.io.OutputStream;
054import java.io.OutputStreamWriter;
055import java.io.StringWriter;
056import java.io.Writer;
057
058import org.hl7.fhir.exceptions.FHIRException;
059import org.hl7.fhir.utilities.MarkDownProcessor;
060import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
061import org.hl7.fhir.utilities.Utilities;
062import org.hl7.fhir.utilities.xml.XhtmlGeneratorAdorner.XhtmlGeneratorAdornerState;
063import org.w3c.dom.Comment;
064import org.w3c.dom.DOMException;
065import org.w3c.dom.Document;
066import org.w3c.dom.Element;
067import org.w3c.dom.Node;
068import org.w3c.dom.ProcessingInstruction;
069import org.w3c.dom.Text;
070
071
072public class XhtmlGenerator {
073
074        private static final int LINE_LIMIT = 85;
075  private XhtmlGeneratorAdorner adorner;
076        
077  public XhtmlGenerator(XhtmlGeneratorAdorner adorner) {
078    super();
079    this.adorner = adorner;
080  }
081
082  public String generateInsert(Document doc, String name, String desc) throws Exception {
083    StringWriter out = new StringWriter();
084    
085    out.write("<div class=\"example\">\r\n");
086    out.write("<p>Example Instance \""+name+"\""+(desc == null ? "" : ": "+Utilities.escapeXml(desc))+"</p>\r\n"); 
087    out.write("<pre class=\"xml\">\r\n");
088
089    XhtmlGeneratorAdornerState state = null; // adorner == null ? new XhtmlGeneratorAdornerState("", "") : adorner.getState(this, null, null);
090    for (int i = 0; i < doc.getChildNodes().getLength(); i++)
091      writeNode(out, doc.getChildNodes().item(i), state, 0);
092    
093    out.write("</pre>\r\n");
094    out.write("</div>\r\n");
095    out.flush();
096    return out.toString();
097  }
098
099  public void generate(Document doc, OutputStream xhtml, String name, String desc, int level, boolean adorn, String filename) throws Exception {
100    adorn = true; // till the xml trick is working
101    
102                OutputStreamWriter out = new OutputStreamWriter(xhtml, "UTF-8");
103                
104    out.write("<div class=\"example\">\r\n");
105    out.write("<p>"+Utilities.escapeXml(desc)+"</p>\r\n"); 
106    if (adorn) {
107      out.write("<pre class=\"xml\">\r\n");
108      out.write("&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\r\n");
109      out.write("\r\n");
110
111      XhtmlGeneratorAdornerState state = null; // adorner == null ? new XhtmlGeneratorAdornerState("", "") : adorner.getState(this, null, null);
112                for (int i = 0; i < doc.getChildNodes().getLength(); i++)
113                        writeNode(out, doc.getChildNodes().item(i), state, level);
114      out.write("</pre>\r\n");
115    } else {
116      out.write("<code class=\"xml\">\r\n");
117      for (int i = 0; i < doc.getChildNodes().getLength(); i++)
118        writeNodePlain(out, doc.getChildNodes().item(i), level);
119      
120      out.write("</code>\r\n");      
121    }
122    out.write("</div>\r\n");
123                out.flush();
124        }
125
126  private void writeNodePlain(Writer out, Node node, int level) throws FHIRException, DOMException, IOException  {
127    if (node.getNodeType() == Node.ELEMENT_NODE)
128      writeElementPlain(out, (Element) node, level);
129    else if (node.getNodeType() == Node.TEXT_NODE)
130      writeTextPlain(out, (Text) node, level);
131    else if (node.getNodeType() == Node.COMMENT_NODE)
132      writeCommentPlain(out, (Comment) node, level);
133    else if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE)
134      writeProcessingInstructionPlain(out, (ProcessingInstruction) node);
135    else if (node.getNodeType() != Node.ATTRIBUTE_NODE)
136      throw new FHIRException("Unhandled node type");
137  }
138
139        private void writeNode(Writer out, Node node, XhtmlGeneratorAdornerState state, int level) throws Exception {
140                if (node.getNodeType() == Node.ELEMENT_NODE)
141                        writeElement(out, (Element) node, state, level);
142                else if (node.getNodeType() == Node.TEXT_NODE)
143                        writeText(out, (Text) node, level);
144                else if (node.getNodeType() == Node.COMMENT_NODE)
145                        writeComment(out, (Comment) node, level);
146                else if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE)
147                        writeProcessingInstruction(out, (ProcessingInstruction) node);
148                else if (node.getNodeType() != Node.ATTRIBUTE_NODE)
149                        throw new FHIRException("Unhandled node type");
150        }
151
152  private void writeProcessingInstruction(Writer out, ProcessingInstruction node) {
153    
154    
155  }
156
157  private void writeProcessingInstructionPlain(Writer out, ProcessingInstruction node) {
158    
159    
160  }
161
162  private void writeComment(Writer out, Comment node, int level) throws DOMException, IOException {
163    String cmt = node.getTextContent();
164    if (cmt.contains(":md:"))
165      cmt = processMarkdown(cmt.replace(":md:", ""));
166    else
167      cmt = escapeHtml(cmt, level);
168    out.write("<span class=\"xmlcomment\">&lt;!-- "+cmt+" --&gt;</span>");
169  }
170
171  private void writeCommentPlain(Writer out, Comment node, int level) throws DOMException, IOException {
172    String cmt = node.getTextContent();
173    if (cmt.contains(":md:"))
174      cmt = processMarkdown(cmt.replace(":md:", ""));
175    else
176      cmt = Utilities.escapeXml(cmt);
177    out.write("<!-- "+cmt+" -->");
178  }
179
180  private String processMarkdown(String text) {
181    return new MarkDownProcessor(Dialect.COMMON_MARK).process(text, "Xhtml generator");
182  }
183
184  private void writeText(Writer out, Text node, int level) throws DOMException, IOException {
185    out.write("<b>"+escapeHtml(Utilities.escapeXml(node.getTextContent()), level)+"</b>");
186  }
187
188  private void writeTextPlain(Writer out, Text node, int level) throws DOMException, IOException {
189    out.write(Utilities.escapeXml(node.getTextContent()));
190  }
191
192  private void writeElement(Writer out, Element node, XhtmlGeneratorAdornerState state, int level) throws Exception {
193    String link = adorner == null ? null : adorner.getLink(this, state, node);
194    if (link != null)
195      out.write("<span class=\"xmltag\">&lt;<a href=\""+link+"\" class=\"xmltag\">"+node.getNodeName()+"</a></span>");
196    else
197      out.write("<span class=\"xmltagred\">&lt;"+node.getNodeName()+"</span>");
198    if (node.hasAttributes()) {
199      out.write("<span class=\"xmlattr\">");
200      out.write("<a name=\""+adorner.getNodeId(state, node)+"\"> </a>");
201      
202      XhtmlGeneratorAdornerState newstate = adorner == null ? new XhtmlGeneratorAdornerState(null, "", "") : adorner.getState(this, state, node);
203      for (int i = 0; i < node.getAttributes().getLength(); i++) {
204        if (i > 0)
205          out.write(" ");
206        if (adorner != null) {
207          XhtmlGeneratorAdornerState attrState = adorner.getAttributeMarkup(this, newstate, node, node.getAttributes().item(i).getNodeName(), node.getAttributes().item(i).getTextContent());
208          out.write(node.getAttributes().item(i).getNodeName()+"=\"<span class=\"xmlattrvalue\">"+attrState.getPrefix()+escapeHtml(Utilities.escapeXml(node.getAttributes().item(i).getTextContent()), level)+attrState.getSuffix()+"</span>\"");
209        } else
210          out.write(node.getAttributes().item(i).getNodeName()+"=\"<span class=\"xmlattrvalue\">"+escapeHtml(Utilities.escapeXml(node.getAttributes().item(i).getTextContent()), level)+"</span>\"");
211      }
212      out.write("</span>");
213
214    }
215    if (node.hasChildNodes()) {
216      out.write("<span class=\"xmltag\">&gt;</span>");
217      if (!node.hasAttributes())
218        out.write("<a name=\""+adorner.getNodeId(state, node)+"\"> </a>");
219      XhtmlGeneratorAdornerState newstate = adorner == null ? new XhtmlGeneratorAdornerState(null, "", "") : adorner.getState(this, state, node);
220      if (newstate.isSuppress())
221        out.write("<span class=\"xmlcomment\">&lt;!-- "+escapeHtml(newstate.getSupressionMessage(), level)+" --&gt;</span>");
222      else {
223        out.write(newstate.getPrefix());
224        for (int i = 0; i < node.getChildNodes().getLength(); i++)
225          writeNode(out, node.getChildNodes().item(i), newstate, level+2);
226
227        out.write(newstate.getSuffix());
228      }
229      if (link != null)
230        out.write("<span class=\"xmltag\">&lt;/<a href=\""+link+"\" class=\"xmltag\">"+node.getNodeName()+"</a>&gt;</span>");
231      else
232        out.write("<span class=\"xmltag\">&lt;/"+node.getNodeName()+"&gt;</span>");
233    }
234    else { 
235      out.write("<span class=\"xmltag\">/&gt;</span>");
236      if (!node.hasAttributes())
237        out.write("<a name=\""+adorner.getNodeId(state, node)+"\"> </a>");
238    }
239    out.write("<a name=\""+adorner.getNodeId(state, node)+"-end\"> </a>");
240        }
241        
242  private void writeElementPlain(Writer out, Element node, int level) throws IOException, FHIRException  {
243    out.write("<"+node.getNodeName());
244    if (node.hasAttributes()) {
245      for (int i = 0; i < node.getAttributes().getLength(); i++) {
246        out.write(" "+node.getAttributes().item(i).getNodeName()+"=\""+Utilities.escapeXml(node.getAttributes().item(i).getTextContent())+"\"");
247      }
248    }
249    if (node.hasChildNodes()) {
250      out.write(">");
251      for (int i = 0; i < node.getChildNodes().getLength(); i++)
252        writeNodePlain(out, node.getChildNodes().item(i), level+2);
253      out.write("</"+node.getNodeName()+">");
254    }
255    else 
256      out.write("/>");
257  }
258  
259        private String escapeHtml(String doco, int indent) {
260                if (doco == null)
261                        return "";
262                
263                int i = 0;
264                StringBuilder b = new StringBuilder();
265                for (char c : doco.toCharArray()) {
266                  i++;
267      if (c == '\r' || c == '\n')
268        i = 0;
269                  if ((i > LINE_LIMIT && c == ' ') || (i > LINE_LIMIT + 15)) {
270                    b.append("\r\n");
271                    for (int j = 0; j < indent; j++)
272                b.append(" ");
273                    i = 0;                  
274                  }
275                  if (c == '<')
276                          b.append("&lt;");
277                  else if (c == '>')
278                          b.append("&gt;");
279                  else if (c == '&')
280                          b.append("&amp;");
281                  else if (c == '\t')
282                          b.append("  ");
283                  else if (c == '\'')
284                          b.append("&apos;");
285                  else if (c == '"')
286                          b.append("&quot;");
287                  else 
288                          b.append(c);
289                }               
290                return b.toString();
291        }       
292}