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}