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}