001package org.hl7.fhir.utilities.xhtml;
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;
035
036import org.hl7.fhir.exceptions.FHIRException;
037import org.hl7.fhir.utilities.Utilities;
038import org.hl7.fhir.utilities.xml.IXMLWriter;
039import org.hl7.fhir.utilities.xml.XMLUtil;
040import org.w3c.dom.Element;
041import org.w3c.dom.Node;
042
043public class CDANarrativeFormat {
044
045  /** 
046   * for a CDA narrative, return the matching XHTML. 
047   * 
048   * For further information, see http://wiki.hl7.org/index.php?title=CDA_Narrative_to_html_mapping
049   * 
050   * @param ed
051   * @return
052   * @throws FHIRException
053   */
054  public XhtmlNode convert(Element ed) throws FHIRException {
055    XhtmlNode div = new XhtmlNode(NodeType.Element, "div");
056    div.setAttribute("xmlns", XhtmlNode.XMLNS);
057    processAttributes(ed, div, "ID", "language", "styleCode");
058    processChildren(ed, div);
059    return div;
060  }
061
062  private void processChildren(Element ed, XhtmlNode x) throws FHIRException {
063    for (Node n : XMLUtil.children(ed)) 
064      processChildNode(n, x);
065  }
066  
067  private void processChildNode(Node n, XhtmlNode xn) throws FHIRException {
068    switch (n.getNodeType()) {
069    case Node.ATTRIBUTE_NODE: 
070    case Node.CDATA_SECTION_NODE:
071    case Node.DOCUMENT_FRAGMENT_NODE: 
072    case Node.DOCUMENT_TYPE_NODE: 
073    case Node.DOCUMENT_NODE: 
074    case Node.ENTITY_NODE: 
075    case Node.PROCESSING_INSTRUCTION_NODE:
076    case Node.NOTATION_NODE:
077      return;
078    case Node.ENTITY_REFERENCE_NODE: 
079      throw new Error("Not handled yet");
080    case Node.COMMENT_NODE: 
081      xn.addComment(n.getTextContent());
082      return;
083    case Node.TEXT_NODE: 
084      if (!Utilities.isWhitespace(n.getTextContent()))
085        xn.addText(n.getTextContent());
086      return;
087    case Node.ELEMENT_NODE:
088      Element e = (Element) n;
089      if (n.getNodeName().equals("br"))
090        processBreak(e, xn);
091      else if (n.getNodeName().equals("caption"))
092        processCaption(e, xn);
093      else if (n.getNodeName().equals("col"))
094        processCol(e, xn);
095      else if (n.getNodeName().equals("colgroup"))
096        processColGroup(e, xn);
097      else if (n.getNodeName().equals("content"))
098        processContent(e, xn);
099      else if (n.getNodeName().equals("footnote"))
100        processFootNote(e, xn);
101      else if (n.getNodeName().equals("footnoteRef"))
102        processFootNodeRef(e, xn);
103      else if (n.getNodeName().equals("item"))
104        processItem(e, xn);
105      else if (n.getNodeName().equals("linkHtml"))
106        processlinkHtml(e, xn);
107      else if (n.getNodeName().equals("list"))
108        processList(e, xn);
109      else if (n.getNodeName().equals("paragraph"))
110        processParagraph(e, xn);
111      else if (n.getNodeName().equals("renderMultiMedia"))
112        processRenderMultiMedia(e, xn);
113      else if (n.getNodeName().equals("sub"))
114        processSub(e, xn);
115      else if (n.getNodeName().equals("sup"))
116        processSup(e, xn);
117      else if (n.getNodeName().equals("table"))
118        processTable(e, xn);
119      else if (n.getNodeName().equals("tbody"))
120        processTBody(e, xn);
121      else if (n.getNodeName().equals("td"))
122        processTd(e, xn);
123      else if (n.getNodeName().equals("tfoot"))
124        processTFoot(e, xn);
125      else if (n.getNodeName().equals("th"))
126        processTh(e, xn);
127      else if (n.getNodeName().equals("thead"))
128        processTHead(e, xn);
129      else if (n.getNodeName().equals("tr"))
130        processTr(e, xn);
131      else
132        throw new FHIRException("Unknown element "+n.getNodeName());
133    }
134  }
135
136  private void processBreak(Element e, XhtmlNode xn) {
137    xn.addTag("br");
138  }
139
140  private void processCaption(Element e, XhtmlNode xn) throws FHIRException {
141    XhtmlNode xc = xn.addTag("h2");
142    processAttributes(e, xc, "ID", "language", "styleCode");
143    processChildren(e, xc);
144  }
145
146  private void processCol(Element e, XhtmlNode xn) throws FHIRException {
147    XhtmlNode xc = xn.addTag("col");
148    processAttributes(e, xc, "ID", "language", "styleCode", "span", "width", "align", "char", "charoff", "valign");
149    processChildren(e, xc);
150  }
151
152  private void processColGroup(Element e, XhtmlNode xn) throws FHIRException {
153    XhtmlNode xc = xn.addTag("colgroup");
154    processAttributes(e, xc, "ID", "language", "styleCode", "span", "width", "align", "char", "charoff", "valign");
155    processChildren(e, xc);
156  }
157
158  private void processContent(Element e, XhtmlNode xn) throws FHIRException {
159    XhtmlNode xc = xn.addTag("span");
160    processAttributes(e, xc, "ID", "language", "styleCode");
161    // todo: do something with revised..., "revised"
162    processChildren(e, xc);
163  }
164
165  private void processFootNote(Element e, XhtmlNode xn) {
166    XhtmlNode xc = xn.addTag("tfoot");
167    processAttributes(e, xc, "ID", "language", "styleCode", "align", "char", "charoff", "valign");
168    processChildren(e, xc);
169  }
170
171  private void processFootNodeRef(Element e, XhtmlNode xn) {
172    throw new Error("element "+e.getNodeName()+" not handled yet");
173  }
174
175  private void processItem(Element e, XhtmlNode xn) throws FHIRException {
176    XhtmlNode xc = xn.addTag("li");
177    processAttributes(e, xc, "ID", "language", "styleCode");
178    processChildren(e, xc);
179  }
180
181  private void processlinkHtml(Element e, XhtmlNode xn) throws FHIRException {
182    XhtmlNode xc = xn.addTag("a");
183    processAttributes(e, xc, "name", "href", "rel", "rev", "title", "ID", "language", "styleCode");
184    processChildren(e, xc);
185  }
186
187  private void processList(Element e, XhtmlNode xn) throws FHIRException {
188    String lt = e.getAttribute("listType");
189    XhtmlNode xc = xn.addTag("ordered".equals(lt) ? "ol" : "ul");
190    processAttributes(e, xc, "ID", "language", "styleCode");
191    processChildren(e, xc);
192  }
193
194  private void processParagraph(Element e, XhtmlNode xn) throws FHIRException {
195    XhtmlNode xc = xn.addTag("p");
196    processAttributes(e, xc, "ID", "language", "styleCode");
197    processChildren(e, xc);
198  }
199
200  private void processRenderMultiMedia(Element e, XhtmlNode xn) throws FHIRException {
201    XhtmlNode xc = xn.addTag("img");
202    String v = e.getAttribute("referencedObject");
203    xc.attribute("src", v);
204    processAttributes(e, xc, "ID", "language", "styleCode");
205    processChildren(e, xc);
206  }
207
208  private void processSub(Element e, XhtmlNode xn) throws FHIRException {
209    XhtmlNode xc = xn.addTag("sub");
210    processChildren(e, xc);
211  }
212
213  private void processSup(Element e, XhtmlNode xn) throws FHIRException {
214    XhtmlNode xc = xn.addTag("sup");
215    processChildren(e, xc);
216  }
217
218  private void processTable(Element e, XhtmlNode xn) throws FHIRException {
219    XhtmlNode xc = xn.addTag("table");
220    processAttributes(e, xc, "ID", "language", "styleCode", "summary", "width", "border", "frame", "rules", "cellspacing", "cellpadding");
221    processChildren(e, xc);
222  }
223
224  private void processTBody(Element e, XhtmlNode xn) throws FHIRException {
225    XhtmlNode xc = xn.addTag("tbody");
226    processAttributes(e, xc, "ID", "language", "styleCode", "align", "char", "charoff", "valign");
227    processChildren(e, xc);
228  }
229
230  private void processTd(Element e, XhtmlNode xn) throws FHIRException {
231    XhtmlNode xc = xn.addTag("td");
232    processAttributes(e, xc, "ID", "language", "styleCode", "abbr", "axis", "headers", "scope", "rowspan", "colspan", "align", "char", "charoff", "valign");
233    processChildren(e, xc);
234  }
235
236  private void processTFoot(Element e, XhtmlNode xn) {
237    XhtmlNode xc = xn.addTag("tfoot");
238    processAttributes(e, xc, "ID", "language", "styleCode", "align", "char", "charoff", "valign");
239    processChildren(e, xc);
240  }
241
242  private void processTh(Element e, XhtmlNode xn) throws FHIRException {
243    XhtmlNode xc = xn.addTag("th");
244    processAttributes(e, xc, "ID", "language", "styleCode", "abbr", "axis", "headers", "scope", "rowspan", "colspan", "align", "char", "charoff", "valign");
245    processChildren(e, xc);
246  }
247
248  private void processTHead(Element e, XhtmlNode xn) throws FHIRException {
249    XhtmlNode xc = xn.addTag("thead");
250    processAttributes(e, xc, "ID", "language", "styleCode", "align", "char", "charoff", "valign");
251    processChildren(e, xc);
252  }
253
254  private void processTr(Element e, XhtmlNode xn) throws FHIRException {
255    XhtmlNode xc = xn.addTag("tr");
256    processAttributes(e, xc, "ID", "language", "styleCode", "align", "char", "charoff", "valign");
257    processChildren(e, xc);
258  }
259
260  private void processAttributes(Element element, XhtmlNode xn, String... names) {
261    for (String n : names) {
262      if (element.hasAttribute(n)) {
263        String v = element.getAttribute(n);
264        switch(n) {
265          case "ID":
266            xn.attribute("id", v);
267            break;
268          case "styleCode":
269            String style = v;
270            switch(v) {
271              // according Table 15.2 CSS rendering, The CDAtm book, Keith W. Boone
272              case "Bold":
273                style = "font-weight: bold";
274                break;
275              case "Underline":
276                style = "text-decoration: underline";
277                break;
278              case "Italics":
279                style = "font-style: italic";
280                break;
281              case "Emphasis":
282                style = "font-weight: small-caps";
283                break;
284              case "Lrule":
285                style = "border-left: 1px";
286                break;
287              case "Rrule":
288                style = "border-right: 1px";
289                break;
290              case "Toprule":
291                style = "border-top: 1px";
292                break;
293              case "Botrule":
294                style = "border-bottom: 1px";
295                break;
296              case "Arabic":
297                style = "list-style-type: decimal";
298                break;
299              case "LittleRoman":
300                style = "list-style-type: lower-roman";
301                break;
302              case "BigRoman":
303                style = "list-style-type: upper-roman";
304                break;
305              case "LittleAlpha":
306                style = "list-style-type: lower-alpha";
307                break;
308              case "BigAlpha":
309                style = "list-style-type: upper-alpha";
310                break;
311              case "Disc":
312                style = "list-style-type: disc";
313                break;
314              case "Circle":
315                style = "list-style-type: circle";
316                break;
317              case "Square":
318                style = "list-style-type: square";
319                break;
320            }
321            xn.attribute("style", style);
322            break;
323          default:
324            xn.attribute(n, v);
325        }
326      }
327    }
328  }
329
330  /**
331   * For XHTML return the matching CDA narrative. This is only guaranteed to work for XML produced from CDA, but will try whatever
332   * @param node
333   * @return
334   * @throws IOException 
335   * @throws FHIRException 
336   */
337  public void convert(IXMLWriter xml, XhtmlNode div) throws IOException, FHIRException {
338    processAttributes(div, xml, "ID", "language", "styleCode");
339    xml.enter("text");
340    processChildren(xml, div);
341    xml.exit("text");
342  }
343
344  private void processChildren(IXMLWriter xml, XhtmlNode x) throws IOException, FHIRException {
345    for (XhtmlNode n : x.getChildNodes()) 
346      processChildNode(xml, n);
347  }
348  
349  private void processChildNode(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
350    switch (n.getNodeType()) {
351    case DocType: 
352    case Document: 
353    case Instruction: 
354      return;
355    case Comment: 
356      xml.comment(n.getContent(), true);
357      return;
358    case Text: 
359      xml.text(n.getContent());
360      return;
361    case Element:
362      if (n.getName().equals("br"))
363        processBreak(xml, n);
364      else if (n.getName().equals("h2"))
365        processCaption(xml, n);
366      else if (n.getName().equals("col"))
367        processCol(xml, n);
368      else if (n.getName().equals("colgroup"))
369        processColGroup(xml, n);
370      else if (n.getName().equals("span"))
371        processContent(xml, n);
372      else if (n.getName().equals("footnote"))
373        processFootNote(xml, n);
374      else if (n.getName().equals("footnoteRef"))
375        processFootNodeRef(xml, n);
376      else if (n.getName().equals("li"))
377        processItem(xml, n);
378      else if (n.getName().equals("linkHtml"))
379        processlinkHtml(xml, n);
380      else if (n.getName().equals("ul") || n.getName().equals("ol"))
381        processList(xml, n);
382      else if (n.getName().equals("p"))
383        processParagraph(xml, n);
384      else if (n.getName().equals("img"))
385        processRenderMultiMedia(xml, n);
386      else if (n.getName().equals("sub"))
387        processSub(xml, n);
388      else if (n.getName().equals("sup"))
389        processSup(xml, n);
390      else if (n.getName().equals("table"))
391        processTable(xml, n);
392      else if (n.getName().equals("tbody"))
393        processTBody(xml, n);
394      else if (n.getName().equals("td"))
395        processTd(xml, n);
396      else if (n.getName().equals("tfoot"))
397        processTFoot(xml, n);
398      else if (n.getName().equals("th"))
399        processTh(xml, n);
400      else if (n.getName().equals("thead"))
401        processTHead(xml, n);
402      else if (n.getName().equals("tr"))
403        processTr(xml, n);
404      else
405        throw new FHIRException("Unknown element "+n.getName());
406    }
407  }
408
409  private void processBreak(IXMLWriter xml, XhtmlNode n) throws IOException {
410    xml.element("br");
411  }
412
413  private void processCaption(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
414    processAttributes(n, xml, "id", "language", "styleCode");
415    xml.enter("caption");
416    processChildren(xml, n);
417    xml.exit("caption");
418  }
419
420  private void processCol(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
421    processAttributes(n, xml, "id", "language", "styleCode", "span", "width", "align", "char", "charoff", "valign");
422    xml.enter("col");
423    processChildren(xml, n);
424    xml.exit("col");
425  }
426
427  private void processColGroup(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
428    processAttributes(n, xml, "id", "language", "styleCode", "span", "width", "align", "char", "charoff", "valign");
429    xml.enter("colgroup");
430    processChildren(xml, n);
431    xml.exit("colgroup");
432  }
433
434  private void processContent(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
435    processAttributes(n, xml, "id", "language", "styleCode");
436    xml.enter("content");
437    // todo: do something with revised..., "revised"
438    processChildren(xml, n);
439    xml.exit("content");
440  }
441
442  private void processFootNote(IXMLWriter xml, XhtmlNode n) {
443    throw new Error("element "+n.getName()+" not handled yet");
444  }
445
446  private void processFootNodeRef(IXMLWriter xml, XhtmlNode n) {
447    throw new Error("element "+n.getName()+" not handled yet");
448  }
449
450  private void processItem(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
451    processAttributes(n, xml, "id", "language", "styleCode");
452    xml.enter("item");
453    processChildren(xml, n);
454    xml.exit("item");
455  }
456
457  private void processlinkHtml(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
458    String v = n.getAttribute("src");
459    xml.attribute("referencedObject", v);
460    processAttributes(n, xml, "name", "href", "rel", "rev", "title", "id", "language", "styleCode");
461    xml.enter("linkHtml");
462    processChildren(xml, n);
463    xml.exit("linkHtml");
464  }
465
466  private void processList(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
467    if (n.getName().equals("ol"))
468      xml.attribute("listType", "ordered");
469    else
470      xml.attribute("listType", "unordered");
471    processAttributes(n, xml, "id", "language", "styleCode");
472    xml.enter("list");
473    processChildren(xml, n);
474    xml.exit("list");
475  }
476
477  private void processParagraph(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
478    processAttributes(n, xml, "id", "language", "styleCode");
479    xml.enter("paragraph");
480    processChildren(xml, n);
481    xml.exit("paragraph");
482  }
483
484  private void processRenderMultiMedia(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
485    String v = n.getAttribute("src");
486    xml.attribute("referencedObject", v);
487    processAttributes(n, xml, "id", "language", "styleCode");
488    xml.enter("renderMultiMedia");
489    processChildren(xml, n);
490    xml.exit("renderMultiMedia");
491  }
492
493  private void processSub(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
494    xml.enter("sub");
495    processChildren(xml, n);
496    xml.exit("sub");
497  }
498
499  private void processSup(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
500    xml.enter("sup");
501    processChildren(xml, n);
502    xml.exit("sup");
503  }
504
505  private void processTable(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
506    processAttributes(n, xml, "id", "language", "styleCode", "summary", "width", "border", "frame", "rules", "cellspacing", "cellpadding");
507    xml.enter("table");
508    processChildren(xml, n);
509    xml.exit("table");
510  }
511
512  private void processTBody(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
513    processAttributes(n, xml, "id", "language", "styleCode", "align", "char", "charoff", "valign");
514    xml.enter("tbody");
515    processChildren(xml, n);
516    xml.exit("tbody");
517  }
518
519  private void processTd(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
520    processAttributes(n, xml, "id", "language", "styleCode", "abbr", "axis", "headers", "scope", "rowspan", "colspan", "align", "char", "charoff", "valign");
521    xml.enter("td");
522    processChildren(xml, n);
523    xml.exit("td");
524  }
525
526  private void processTFoot(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
527    processAttributes(n, xml, "id", "language", "styleCode", "align", "char", "charoff", "valign");
528    xml.enter("tfoot");
529    processChildren(xml, n);
530    xml.exit("tfoot");
531  }
532
533  private void processTh(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
534    processAttributes(n, xml, "id", "language", "styleCode", "abbr", "axis", "headers", "scope", "rowspan", "colspan", "align", "char", "charoff", "valign");
535    xml.enter("th");
536    processChildren(xml, n);
537    xml.exit("th");
538  }
539
540  private void processTHead(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
541    processAttributes(n, xml, "id", "language", "styleCode", "align", "char", "charoff", "valign");
542    xml.enter("thead");
543    processChildren(xml, n);
544    xml.exit("thead");
545  }
546
547  private void processTr(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
548    processAttributes(n, xml, "id", "language", "styleCode", "align", "char", "charoff", "valign");
549    xml.enter("tr");
550    processChildren(xml, n);
551    xml.exit("tr");
552  }
553
554  private void processAttributes(XhtmlNode xn, IXMLWriter xml, String... names) throws IOException {
555    for (String n : names) {
556      if (xn.hasAttribute(n)) {
557        String v = xn.getAttribute(n);
558        switch(n) {
559          case "id":
560            xml.attribute("ID", v);
561            break;
562          case "style":
563            String style = v;
564            switch(v) {
565              // according Table 15.2 CSS rendering, The CDAtm book, Keith W. Boone, will not cover everything, just reverse of processAttributes
566              case "font-weight: bold":
567                style = "Bold";
568                break;
569              case "text-decoration: underline":
570                style = "Underline";
571                break;
572              case "font-style: italic":
573                style = "Italics";
574                break;
575              case "font-weight: small-caps":
576                style = "Emphasis";
577                break;
578              case "border-left: 1px":
579                style = "Lrule";
580                break;
581              case "border-right: 1px":
582                style = "Rrule";
583                break;
584              case "border-top: 1px":
585                style = "Toprule";
586                break;
587              case "border-bottom: 1px":
588                style = "Botrule";
589                break;
590              case "List-style-type: decimal":
591                style = "Arabic";
592                break;
593              case "list-style-type: lower-roman":
594                style = "LittleRoman";
595                break;
596              case "list-style-type: upper-roman":
597                style = "BigRoman";
598                break;
599              case "list-style-type: lower-alpha":
600                style = "LittleAlpha";
601                break;
602              case "list-style-type: upper-alpha":
603                style = "BigAlpha";
604                break;
605              case "list-style-type: disc":
606                style = "Disc";
607                break;
608              case "list-style-type: circle":
609                style = "Circle";
610                break;
611              case "list-style-type: square":
612                style = "Square";
613                break;
614            }
615            xml.attribute("styleCode", style);
616            break;
617          default:
618            xml.attribute(n, v);
619        }
620      }
621    }
622  }
623
624
625}