001package org.hl7.fhir.utilities.xhtml;
002
003import java.io.IOException;
004
005import org.hl7.fhir.exceptions.FHIRException;
006import org.hl7.fhir.utilities.Utilities;
007import org.hl7.fhir.utilities.xml.IXMLWriter;
008import org.hl7.fhir.utilities.xml.XMLUtil;
009import org.w3c.dom.Element;
010import org.w3c.dom.Node;
011
012public class CDANarrativeFormat {
013
014  /** 
015   * for a CDA narrative, return the matching XHTML. 
016   * 
017   * For further information, see http://wiki.hl7.org/index.php?title=CDA_Narrative_to_html_mapping
018   * 
019   * @param ed
020   * @return
021   * @throws FHIRException
022   */
023  public XhtmlNode convert(Element ed) throws FHIRException {
024    XhtmlNode div = new XhtmlNode(NodeType.Element, "div");
025    processAttributes(ed, div, "ID", "language", "styleCode");
026    processChildren(ed, div);
027    return div;
028  }
029
030  private void processChildren(Element ed, XhtmlNode x) throws FHIRException {
031    for (Node n : XMLUtil.children(ed)) 
032      processChildNode(n, x);
033  }
034  
035  private void processChildNode(Node n, XhtmlNode xn) throws FHIRException {
036    switch (n.getNodeType()) {
037    case Node.ATTRIBUTE_NODE: 
038    case Node.CDATA_SECTION_NODE:
039    case Node.DOCUMENT_FRAGMENT_NODE: 
040    case Node.DOCUMENT_TYPE_NODE: 
041    case Node.DOCUMENT_NODE: 
042    case Node.ENTITY_NODE: 
043    case Node.PROCESSING_INSTRUCTION_NODE:
044    case Node.NOTATION_NODE:
045      return;
046    case Node.ENTITY_REFERENCE_NODE: 
047      throw new Error("Not handled yet");
048    case Node.COMMENT_NODE: 
049      xn.addComment(n.getTextContent());
050      return;
051    case Node.TEXT_NODE: 
052      if (!Utilities.isWhitespace(n.getTextContent()))
053        xn.addText(n.getTextContent());
054      return;
055    case Node.ELEMENT_NODE:
056      Element e = (Element) n;
057      if (n.getNodeName().equals("br"))
058        processBreak(e, xn);
059      else if (n.getNodeName().equals("caption"))
060        processCaption(e, xn);
061      else if (n.getNodeName().equals("col"))
062        processCol(e, xn);
063      else if (n.getNodeName().equals("colgroup"))
064        processColGroup(e, xn);
065      else if (n.getNodeName().equals("content"))
066        processContent(e, xn);
067      else if (n.getNodeName().equals("footnote"))
068        processFootNote(e, xn);
069      else if (n.getNodeName().equals("footnoteRef"))
070        processFootNodeRef(e, xn);
071      else if (n.getNodeName().equals("item"))
072        processItem(e, xn);
073      else if (n.getNodeName().equals("linkHtml"))
074        processlinkHtml(e, xn);
075      else if (n.getNodeName().equals("list"))
076        processList(e, xn);
077      else if (n.getNodeName().equals("paragraph"))
078        processParagraph(e, xn);
079      else if (n.getNodeName().equals("renderMultiMedia"))
080        processRenderMultiMedia(e, xn);
081      else if (n.getNodeName().equals("sub"))
082        processSub(e, xn);
083      else if (n.getNodeName().equals("sup"))
084        processSup(e, xn);
085      else if (n.getNodeName().equals("table"))
086        processTable(e, xn);
087      else if (n.getNodeName().equals("tbody"))
088        processTBody(e, xn);
089      else if (n.getNodeName().equals("td"))
090        processTd(e, xn);
091      else if (n.getNodeName().equals("tfoot"))
092        processTFoot(e, xn);
093      else if (n.getNodeName().equals("th"))
094        processTh(e, xn);
095      else if (n.getNodeName().equals("thead"))
096        processTHead(e, xn);
097      else if (n.getNodeName().equals("tr"))
098        processTr(e, xn);
099      else
100        throw new FHIRException("Unknown element "+n.getNodeName());
101    }
102  }
103
104  private void processBreak(Element e, XhtmlNode xn) {
105    xn.addTag("br");
106  }
107
108  private void processCaption(Element e, XhtmlNode xn) throws FHIRException {
109    XhtmlNode xc = xn.addTag("h2");
110    processAttributes(e, xc, "ID", "language", "styleCode");
111    processChildren(e, xc);
112  }
113
114  private void processCol(Element e, XhtmlNode xn) throws FHIRException {
115    XhtmlNode xc = xn.addTag("col");
116    processAttributes(e, xc, "ID", "language", "styleCode", "span", "width", "align", "char", "charoff", "valign");
117    processChildren(e, xc);
118  }
119
120  private void processColGroup(Element e, XhtmlNode xn) throws FHIRException {
121    XhtmlNode xc = xn.addTag("colgroup");
122    processAttributes(e, xc, "ID", "language", "styleCode", "span", "width", "align", "char", "charoff", "valign");
123    processChildren(e, xc);
124  }
125
126  private void processContent(Element e, XhtmlNode xn) throws FHIRException {
127    XhtmlNode xc = xn.addTag("span");
128    processAttributes(e, xc, "ID", "language", "styleCode");
129    // todo: do something with revised..., "revised"
130    processChildren(e, xc);
131  }
132
133  private void processFootNote(Element e, XhtmlNode xn) {
134    throw new Error("element "+e.getNodeName()+" not handled yet");
135  }
136
137  private void processFootNodeRef(Element e, XhtmlNode xn) {
138    throw new Error("element "+e.getNodeName()+" not handled yet");
139  }
140
141  private void processItem(Element e, XhtmlNode xn) throws FHIRException {
142    XhtmlNode xc = xn.addTag("li");
143    processAttributes(e, xc, "ID", "language", "styleCode");
144    processChildren(e, xc);
145  }
146
147  private void processlinkHtml(Element e, XhtmlNode xn) throws FHIRException {
148    XhtmlNode xc = xn.addTag("a");
149    processAttributes(e, xc, "name", "href", "rel", "rev", "title", "ID", "language", "styleCode");
150    processChildren(e, xc);
151  }
152
153  private void processList(Element e, XhtmlNode xn) throws FHIRException {
154    String lt = e.getAttribute("listType");
155    XhtmlNode xc = xn.addTag("ordered".equals(lt) ? "ol" : "ul");
156    processAttributes(e, xc, "ID", "language", "styleCode");
157    processChildren(e, xc);
158  }
159
160  private void processParagraph(Element e, XhtmlNode xn) throws FHIRException {
161    XhtmlNode xc = xn.addTag("p");
162    processAttributes(e, xc, "ID", "language", "styleCode");
163    processChildren(e, xc);
164  }
165
166  private void processRenderMultiMedia(Element e, XhtmlNode xn) throws FHIRException {
167    XhtmlNode xc = xn.addTag("img");
168    String v = e.getAttribute("referencedObject");
169    xn.attribute("src", v);
170    processAttributes(e, xc, "ID", "language", "styleCode");
171    processChildren(e, xc);
172  }
173
174  private void processSub(Element e, XhtmlNode xn) throws FHIRException {
175    XhtmlNode xc = xn.addTag("sub");
176    processChildren(e, xc);
177  }
178
179  private void processSup(Element e, XhtmlNode xn) throws FHIRException {
180    XhtmlNode xc = xn.addTag("sup");
181    processChildren(e, xc);
182  }
183
184  private void processTable(Element e, XhtmlNode xn) throws FHIRException {
185    XhtmlNode xc = xn.addTag("table");
186    processAttributes(e, xc, "ID", "language", "styleCode", "summary", "width", "border", "frame", "rules", "cellspacing", "cellpadding");
187    processChildren(e, xc);
188  }
189
190  private void processTBody(Element e, XhtmlNode xn) throws FHIRException {
191    XhtmlNode xc = xn.addTag("tbody");
192    processAttributes(e, xc, "ID", "language", "styleCode", "align", "char", "charoff", "valign");
193    processChildren(e, xc);
194  }
195
196  private void processTd(Element e, XhtmlNode xn) throws FHIRException {
197    XhtmlNode xc = xn.addTag("td");
198    processAttributes(e, xc, "ID", "language", "styleCode", "abbr", "axis", "headers", "scope", "rowspan", "colspan", "align", "char", "charoff", "valign");
199    processChildren(e, xc);
200  }
201
202  private void processTFoot(Element e, XhtmlNode xn) {
203    throw new Error("element "+e.getNodeName()+" not handled yet");
204  }
205
206  private void processTh(Element e, XhtmlNode xn) throws FHIRException {
207    XhtmlNode xc = xn.addTag("th");
208    processAttributes(e, xc, "ID", "language", "styleCode", "abbr", "axis", "headers", "scope", "rowspan", "colspan", "align", "char", "charoff", "valign");
209    processChildren(e, xc);
210  }
211
212  private void processTHead(Element e, XhtmlNode xn) throws FHIRException {
213    XhtmlNode xc = xn.addTag("thead");
214    processAttributes(e, xc, "ID", "language", "styleCode", "align", "char", "charoff", "valign");
215    processChildren(e, xc);
216  }
217
218  private void processTr(Element e, XhtmlNode xn) throws FHIRException {
219    XhtmlNode xc = xn.addTag("tr");
220    processAttributes(e, xc, "ID", "language", "styleCode", "align", "char", "charoff", "valign");
221    processChildren(e, xc);
222  }
223
224  private void processAttributes(Element element, XhtmlNode xn, String... names) {
225    for (String n : names) {
226      if (element.hasAttribute(n)) {
227        String v = element.getAttribute(n);
228        if (n.equals("ID"))
229          xn.attribute("id", v);
230        else
231          xn.attribute(n, v);
232      }
233    }
234  }
235
236  /**
237   * For XHTML return the matching CDA narrative. This is only guaranteed to work for XML produced from CDA, but will try whatever
238   * @param node
239   * @return
240   * @throws IOException 
241   * @throws FHIRException 
242   */
243  public void convert(IXMLWriter xml, XhtmlNode div) throws IOException, FHIRException {
244    processAttributes(div, xml, "ID", "language", "styleCode");
245    xml.enter("text");
246    processChildren(xml, div);
247    xml.exit("text");
248  }
249
250  private void processChildren(IXMLWriter xml, XhtmlNode x) throws IOException, FHIRException {
251    for (XhtmlNode n : x.getChildNodes()) 
252      processChildNode(xml, n);
253  }
254  
255  private void processChildNode(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
256    switch (n.getNodeType()) {
257    case DocType: 
258    case Document: 
259    case Instruction: 
260      return;
261    case Comment: 
262      xml.comment(n.getContent(), true);
263      return;
264    case Text: 
265      xml.text(n.getContent());
266      return;
267    case Element:
268      if (n.getName().equals("br"))
269        processBreak(xml, n);
270      else if (n.getName().equals("h2"))
271        processCaption(xml, n);
272      else if (n.getName().equals("col"))
273        processCol(xml, n);
274      else if (n.getName().equals("colgroup"))
275        processColGroup(xml, n);
276      else if (n.getName().equals("span"))
277        processContent(xml, n);
278      else if (n.getName().equals("footnote"))
279        processFootNote(xml, n);
280      else if (n.getName().equals("footnoteRef"))
281        processFootNodeRef(xml, n);
282      else if (n.getName().equals("li"))
283        processItem(xml, n);
284      else if (n.getName().equals("linkHtml"))
285        processlinkHtml(xml, n);
286      else if (n.getName().equals("ul") || n.getName().equals("ol"))
287        processList(xml, n);
288      else if (n.getName().equals("p"))
289        processParagraph(xml, n);
290      else if (n.getName().equals("img"))
291        processRenderMultiMedia(xml, n);
292      else if (n.getName().equals("sub"))
293        processSub(xml, n);
294      else if (n.getName().equals("sup"))
295        processSup(xml, n);
296      else if (n.getName().equals("table"))
297        processTable(xml, n);
298      else if (n.getName().equals("tbody"))
299        processTBody(xml, n);
300      else if (n.getName().equals("td"))
301        processTd(xml, n);
302      else if (n.getName().equals("tfoot"))
303        processTFoot(xml, n);
304      else if (n.getName().equals("th"))
305        processTh(xml, n);
306      else if (n.getName().equals("thead"))
307        processTHead(xml, n);
308      else if (n.getName().equals("tr"))
309        processTr(xml, n);
310      else
311        throw new FHIRException("Unknown element "+n.getName());
312    }
313  }
314
315  private void processBreak(IXMLWriter xml, XhtmlNode n) throws IOException {
316    xml.element("br");
317  }
318
319  private void processCaption(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
320    processAttributes(n, xml, "id", "language", "styleCode");
321    xml.enter("caption");
322    processChildren(xml, n);
323    xml.exit("caption");
324  }
325
326  private void processCol(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
327    processAttributes(n, xml, "id", "language", "styleCode", "span", "width", "align", "char", "charoff", "valign");
328    xml.enter("col");
329    processChildren(xml, n);
330    xml.exit("col");
331  }
332
333  private void processColGroup(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
334    processAttributes(n, xml, "id", "language", "styleCode", "span", "width", "align", "char", "charoff", "valign");
335    xml.enter("colgroup");
336    processChildren(xml, n);
337    xml.exit("colgroup");
338  }
339
340  private void processContent(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
341    processAttributes(n, xml, "id", "language", "styleCode");
342    xml.enter("content");
343    // todo: do something with revised..., "revised"
344    processChildren(xml, n);
345    xml.exit("content");
346  }
347
348  private void processFootNote(IXMLWriter xml, XhtmlNode n) {
349    throw new Error("element "+n.getName()+" not handled yet");
350  }
351
352  private void processFootNodeRef(IXMLWriter xml, XhtmlNode n) {
353    throw new Error("element "+n.getName()+" not handled yet");
354  }
355
356  private void processItem(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
357    processAttributes(n, xml, "id", "language", "styleCode");
358    xml.enter("item");
359    processChildren(xml, n);
360    xml.exit("item");
361  }
362
363  private void processlinkHtml(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
364    String v = n.getAttribute("src");
365    xml.attribute("referencedObject", v);
366    processAttributes(n, xml, "name", "href", "rel", "rev", "title", "id", "language", "styleCode");
367    xml.enter("linkHtml");
368    processChildren(xml, n);
369    xml.exit("linkHtml");
370  }
371
372  private void processList(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
373    if (n.getName().equals("ol"))
374      xml.attribute("listType", "ordered");
375    else
376      xml.attribute("listType", "unordered");
377    processAttributes(n, xml, "id", "language", "styleCode");
378    xml.enter("list");
379    processChildren(xml, n);
380    xml.exit("list");
381  }
382
383  private void processParagraph(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
384    processAttributes(n, xml, "id", "language", "styleCode");
385    xml.enter("paragraph");
386    processChildren(xml, n);
387    xml.exit("paragraph");
388  }
389
390  private void processRenderMultiMedia(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
391    String v = n.getAttribute("src");
392    xml.attribute("referencedObject", v);
393    processAttributes(n, xml, "id", "language", "styleCode");
394    xml.enter("renderMultiMedia");
395    processChildren(xml, n);
396    xml.exit("renderMultiMedia");
397  }
398
399  private void processSub(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
400    xml.enter("sub");
401    processChildren(xml, n);
402    xml.exit("sub");
403  }
404
405  private void processSup(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
406    xml.enter("sup");
407    processChildren(xml, n);
408    xml.exit("sup");
409  }
410
411  private void processTable(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
412    processAttributes(n, xml, "id", "language", "styleCode", "summary", "width", "border", "frame", "rules", "cellspacing", "cellpadding");
413    xml.enter("table");
414    processChildren(xml, n);
415    xml.exit("table");
416  }
417
418  private void processTBody(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
419    processAttributes(n, xml, "id", "language", "styleCode", "align", "char", "charoff", "valign");
420    xml.enter("tbody");
421    processChildren(xml, n);
422    xml.exit("tbody");
423  }
424
425  private void processTd(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
426    processAttributes(n, xml, "id", "language", "styleCode", "abbr", "axis", "headers", "scope", "rowspan", "colspan", "align", "char", "charoff", "valign");
427    xml.enter("td");
428    processChildren(xml, n);
429    xml.exit("td");
430  }
431
432  private void processTFoot(IXMLWriter xml, XhtmlNode n) {
433    throw new Error("element "+n.getName()+" not handled yet");
434  }
435
436  private void processTh(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
437    processAttributes(n, xml, "id", "language", "styleCode", "abbr", "axis", "headers", "scope", "rowspan", "colspan", "align", "char", "charoff", "valign");
438    xml.enter("th");
439    processChildren(xml, n);
440    xml.exit("th");
441  }
442
443  private void processTHead(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
444    processAttributes(n, xml, "id", "language", "styleCode", "align", "char", "charoff", "valign");
445    xml.enter("thead");
446    processChildren(xml, n);
447    xml.exit("thead");
448  }
449
450  private void processTr(IXMLWriter xml, XhtmlNode n) throws IOException, FHIRException {
451    processAttributes(n, xml, "id", "language", "styleCode", "align", "char", "charoff", "valign");
452    xml.enter("tr");
453    processChildren(xml, n);
454    xml.exit("tr");
455  }
456
457  private void processAttributes(XhtmlNode xn, IXMLWriter xml, String... names) throws IOException {
458    for (String n : names) {
459      if (xn.hasAttribute(n)) {
460        String v = xn.getAttribute(n);
461        if (n.equals("id"))
462          xml.attribute("ID", v);
463        else
464          xml.attribute(n, v);
465      }
466    }
467  }
468
469
470}