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