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; 035import java.io.Serializable; 036import java.util.ArrayList; 037import java.util.HashMap; 038import java.util.List; 039import java.util.Map; 040 041import org.hl7.fhir.exceptions.DefinitionException; 042import org.hl7.fhir.exceptions.FHIRException; 043import org.hl7.fhir.exceptions.FHIRFormatError; 044import org.hl7.fhir.instance.model.api.IBaseXhtml; 045import org.hl7.fhir.utilities.MarkDownProcessor; 046import org.hl7.fhir.utilities.Utilities; 047import org.hl7.fhir.utilities.MarkDownProcessor.Dialect; 048import org.hl7.fhir.utilities.i18n.I18nConstants; 049import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 050 051import ca.uhn.fhir.model.api.annotation.ChildOrder; 052import ca.uhn.fhir.model.primitive.XhtmlDt; 053 054import static org.apache.commons.lang3.StringUtils.isNotBlank; 055 056@ca.uhn.fhir.model.api.annotation.DatatypeDef(name="xhtml") 057public class XhtmlNode implements IBaseXhtml { 058 private static final long serialVersionUID = -4362547161441436492L; 059 060 061 public static class Location implements Serializable { 062 private static final long serialVersionUID = -4079302502900219721L; 063 private int line; 064 private int column; 065 public Location(int line, int column) { 066 super(); 067 this.line = line; 068 this.column = column; 069 } 070 public int getLine() { 071 return line; 072 } 073 public int getColumn() { 074 return column; 075 } 076 @Override 077 public String toString() { 078 return "Line "+Integer.toString(line)+", column "+Integer.toString(column); 079 } 080 } 081 082 public static final String NBSP = Character.toString((char)0xa0); 083 public static final String XMLNS = "http://www.w3.org/1999/xhtml"; 084 private static final String DECL_XMLNS = " xmlns=\""+XMLNS+"\""; 085 086 private Location location; 087 private NodeType nodeType; 088 private String name; 089 private Map<String, String> attributes = new HashMap<String, String>(); 090 private List<XhtmlNode> childNodes = new ArrayList<XhtmlNode>(); 091 private String content; 092 private boolean notPretty; 093 private boolean inPara; 094 private boolean inLink; 095 private boolean seperated; 096 097 public XhtmlNode() { 098 super(); 099 } 100 101 102 public XhtmlNode(NodeType nodeType, String name) { 103 super(); 104 this.nodeType = nodeType; 105 this.name = name; 106 } 107 108 public XhtmlNode(NodeType nodeType) { 109 super(); 110 this.nodeType = nodeType; 111 } 112 113 public NodeType getNodeType() { 114 return nodeType; 115 } 116 117 public void setNodeType(NodeType nodeType) { 118 this.nodeType = nodeType; 119 } 120 121 public String getName() { 122 return name; 123 } 124 125 public XhtmlNode setName(String name) { 126 assert name.contains(":") == false : "Name should not contain any : but was " + name; 127 this.name = name; 128 return this; 129 } 130 131 public Map<String, String> getAttributes() { 132 return attributes; 133 } 134 135 public List<XhtmlNode> getChildNodes() { 136 return childNodes; 137 } 138 139 public String getContent() { 140 return content; 141 } 142 143 public XhtmlNode setContent(String content) { 144 if (!(nodeType != NodeType.Text || nodeType != NodeType.Comment)) 145 throw new Error("Wrong node type"); 146 this.content = content; 147 return this; 148 } 149 150 public void validate(List<String> errors, String path, boolean inResource, boolean inPara, boolean inLink) { 151 if (nodeType == NodeType.Element || nodeType == NodeType.Document) { 152 path = Utilities.noString(path) ? name : path+"/"+name; 153 if (inResource) { 154 if (!Utilities.existsInList(name, "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong", 155 "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup", 156 "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td", 157 "code", "samp", "img", "map", "area")) { 158 errors.add("Error at "+path+": Found "+name+" in a resource"); 159 } 160 for (String an : attributes.keySet()) { 161 boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, 162 "title", "style", "class", "ID", "lang", "xml:lang", "dir", "accesskey", "tabindex", 163 // tables 164 "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") || 165 Utilities.existsInList(name + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite", 166 "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src", 167 "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape", 168 "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border", 169 "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap" 170 ); 171 if (!ok) 172 errors.add("Error at "+path+": Found attribute "+name+"."+an+" in a resource"); 173 } 174 } 175 if (inPara && Utilities.existsInList(name, "div", "blockquote", "table", "ol", "ul", "p")) { 176 errors.add("Error at "+path+": Found "+name+" inside an html paragraph"); 177 } 178 if (inLink && Utilities.existsInList(name, "a")) { 179 errors.add("Error at "+path+": Found an <a> inside an <a> paragraph"); 180 } 181 182 if (childNodes != null) { 183 if ("p".equals(name)) { 184 inPara = true; 185 } 186 if ("a".equals(name)) { 187 inLink = true; 188 } 189 for (XhtmlNode child : childNodes) { 190 child.validate(errors, path, inResource, inPara, inLink); 191 } 192 } 193 } 194 } 195 196 public XhtmlNode addTag(String name) 197 { 198 199 if (!(nodeType == NodeType.Element || nodeType == NodeType.Document)) { 200 throw new Error("Wrong node type - node is "+nodeType.toString()+" ('"+getName()+"/"+getContent()+"')"); 201 } 202 203// if (inPara && name.equals("p")) { 204// throw new FHIRException("nested Para"); 205// } 206// if (inLink && name.equals("a")) { 207// throw new FHIRException("Nested Link"); 208// } 209 XhtmlNode node = new XhtmlNode(NodeType.Element); 210 node.setName(name); 211 if (inPara || name.equals("p")) { 212 node.inPara = true; 213 } 214 if (inLink || name.equals("a")) { 215 node.inLink = true; 216 } 217 childNodes.add(node); 218 return node; 219 } 220 221 public XhtmlNode addTag(int index, String name) 222 { 223 224 if (!(nodeType == NodeType.Element || nodeType == NodeType.Document)) 225 throw new Error("Wrong node type. is "+nodeType.toString()); 226 XhtmlNode node = new XhtmlNode(NodeType.Element); 227 if (inPara || name.equals("p")) { 228 node.inPara = true; 229 } 230 if (inLink || name.equals("a")) { 231 node.inLink = true; 232 } 233 node.setName(name); 234 childNodes.add(index, node); 235 return node; 236 } 237 238 public XhtmlNode addComment(String content) 239 { 240 if (!(nodeType == NodeType.Element || nodeType == NodeType.Document)) 241 throw new Error("Wrong node type"); 242 XhtmlNode node = new XhtmlNode(NodeType.Comment); 243 node.setContent(content); 244 childNodes.add(node); 245 return node; 246 } 247 248 public XhtmlNode addDocType(String content) 249 { 250 if (!(nodeType == NodeType.Document)) 251 throw new Error("Wrong node type"); 252 XhtmlNode node = new XhtmlNode(NodeType.DocType); 253 node.setContent(content); 254 childNodes.add(node); 255 return node; 256 } 257 258 public XhtmlNode addInstruction(String content) 259 { 260 if (!(nodeType == NodeType.Document)) 261 throw new Error("Wrong node type"); 262 XhtmlNode node = new XhtmlNode(NodeType.Instruction); 263 node.setContent(content); 264 childNodes.add(node); 265 return node; 266 } 267 public XhtmlNode addText(String content) 268 { 269 if (!(nodeType == NodeType.Element || nodeType == NodeType.Document)) 270 throw new Error("Wrong node type"); 271 if (content != null) { 272 XhtmlNode node = new XhtmlNode(NodeType.Text); 273 node.setContent(content); 274 childNodes.add(node); 275 return node; 276 } else 277 return null; 278 } 279 280 public XhtmlNode addText(int index, String content) 281 { 282 if (!(nodeType == NodeType.Element || nodeType == NodeType.Document)) 283 throw new Error("Wrong node type"); 284 if (content == null) 285 throw new Error("Content cannot be null"); 286 287 XhtmlNode node = new XhtmlNode(NodeType.Text); 288 node.setContent(content); 289 childNodes.add(index, node); 290 return node; 291 } 292 293 public boolean allChildrenAreText() 294 { 295 boolean res = true; 296 for (XhtmlNode n : childNodes) 297 res = res && n.getNodeType() == NodeType.Text; 298 return res; 299 } 300 301 public XhtmlNode getElement(String name) { 302 for (XhtmlNode n : childNodes) 303 if (n.getNodeType() == NodeType.Element && name.equals(n.getName())) 304 return n; 305 return null; 306 } 307 308 public XhtmlNode getFirstElement() { 309 for (XhtmlNode n : childNodes) 310 if (n.getNodeType() == NodeType.Element) 311 return n; 312 return null; 313 } 314 315 public String allText() { 316 if (childNodes == null || childNodes.isEmpty()) 317 return getContent(); 318 319 StringBuilder b = new StringBuilder(); 320 for (XhtmlNode n : childNodes) 321 if (n.getNodeType() == NodeType.Text) 322 b.append(n.getContent()); 323 else if (n.getNodeType() == NodeType.Element) 324 b.append(n.allText()); 325 return b.toString(); 326 } 327 328 public XhtmlNode attribute(String name, String value) { 329 if (!(nodeType == NodeType.Element || nodeType == NodeType.Document)) 330 throw new Error("Wrong node type"); 331 if (name == null) 332 throw new Error("name is null"); 333 if (value == null) 334 throw new Error("value is null"); 335 attributes.put(name, value); 336 return this; 337 } 338 339 public boolean hasAttribute(String name) { 340 return getAttributes().containsKey(name); 341 } 342 343 public String getAttribute(String name) { 344 return getAttributes().get(name); 345 } 346 347 public XhtmlNode setAttribute(String name, String value) { 348 if (nodeType != NodeType.Element) { 349 throw new Error("Attempt to set an attribute on something that is not an element"); 350 } 351 getAttributes().put(name, value); 352 return this; 353 } 354 355 public XhtmlNode copy() { 356 XhtmlNode dst = new XhtmlNode(nodeType); 357 dst.name = name; 358 for (String n : attributes.keySet()) { 359 dst.attributes.put(n, attributes.get(n)); 360 } 361 for (XhtmlNode n : childNodes) 362 dst.childNodes.add(n.copy()); 363 dst.content = content; 364 return dst; 365 } 366 367 @Override 368 public boolean isEmpty() { 369 return (childNodes == null || childNodes.isEmpty()) && content == null; 370 } 371 372 public boolean equalsDeep(XhtmlNode other) { 373 if (other == null) { 374 return false; 375 } 376 377 if (!(nodeType == other.nodeType) || !compare(name, other.name) || !compare(content, other.content)) 378 return false; 379 if (attributes.size() != other.attributes.size()) 380 return false; 381 for (String an : attributes.keySet()) 382 if (!attributes.get(an).equals(other.attributes.get(an))) 383 return false; 384 if (childNodes.size() != other.childNodes.size()) 385 return false; 386 for (int i = 0; i < childNodes.size(); i++) { 387 if (!compareDeep(childNodes.get(i), other.childNodes.get(i))) 388 return false; 389 } 390 return true; 391 } 392 393 private boolean compare(String s1, String s2) { 394 if (s1 == null && s2 == null) 395 return true; 396 if (s1 == null || s2 == null) 397 return false; 398 return s1.equals(s2); 399 } 400 401 private static boolean compareDeep(XhtmlNode e1, XhtmlNode e2) { 402 if (e1 == null && e2 == null) 403 return true; 404 if (e1 == null || e2 == null) 405 return false; 406 return e1.equalsDeep(e2); 407 } 408 409 public String getNsDecl() { 410 for (String an : attributes.keySet()) { 411 if (an.equals("xmlns")) { 412 return attributes.get(an); 413 } 414 } 415 return null; 416 } 417 418 419 @Override 420 public String getValueAsString() { 421 if (isEmpty()) { 422 return null; 423 } 424 try { 425 String retVal = new XhtmlComposer(XhtmlComposer.XML).compose(this); 426 retVal = XhtmlDt.preprocessXhtmlNamespaceDeclaration(retVal); 427 return retVal; 428 } catch (Exception e) { 429 // TODO: composer shouldn't throw exception like this 430 throw new RuntimeException(e); 431 } 432 } 433 434 @Override 435 public void setValueAsString(String theValue) throws IllegalArgumentException { 436 this.attributes = null; 437 this.childNodes = null; 438 this.content = null; 439 this.name = null; 440 this.nodeType= null; 441 if (theValue == null || theValue.length() == 0) { 442 return; 443 } 444 445 String val = theValue.trim(); 446 447 if (!val.startsWith("<")) { 448 val = "<div" + DECL_XMLNS +">" + val + "</div>"; 449 } 450 if (val.startsWith("<?") && val.endsWith("?>")) { 451 return; 452 } 453 454 val = XhtmlDt.preprocessXhtmlNamespaceDeclaration(val); 455 456 try { 457 XhtmlDocument fragment = new XhtmlParser().parse(val, "div"); 458 this.attributes = fragment.getAttributes(); 459 this.childNodes = fragment.getChildNodes(); 460 // Strip the <? .. ?> declaration if one was present 461 if (childNodes.size() > 0 && childNodes.get(0) != null && childNodes.get(0).getNodeType() == NodeType.Instruction) { 462 childNodes.remove(0); 463 } 464 this.content = fragment.getContent(); 465 this.name = fragment.getName(); 466 this.nodeType= fragment.getNodeType(); 467 } catch (Exception e) { 468 // TODO: composer shouldn't throw exception like this 469 throw new RuntimeException(e); 470 } 471 472 } 473 474 public XhtmlNode getElementByIndex(int i) { 475 int c = 0; 476 for (XhtmlNode n : childNodes) 477 if (n.getNodeType() == NodeType.Element) { 478 if (c == i) 479 return n; 480 else 481 c++; 482 } 483 return null; 484 } 485 486 @Override 487 public String getValue() { 488 return getValueAsString(); 489 } 490 491 public boolean hasValue() { 492 return isNotBlank(getValueAsString()); 493 } 494 495 @Override 496 public XhtmlNode setValue(String theValue) throws IllegalArgumentException { 497 setValueAsString(theValue); 498 return this; 499 } 500 501 /** 502 * Returns false 503 */ 504 public boolean hasFormatComment() { 505 return false; 506 } 507 508 /** 509 * NOT SUPPORTED - Throws {@link UnsupportedOperationException} 510 */ 511 public List<String> getFormatCommentsPre() { 512 throw new UnsupportedOperationException(); 513 } 514 515 /** 516 * NOT SUPPORTED - Throws {@link UnsupportedOperationException} 517 */ 518 public List<String> getFormatCommentsPost() { 519 throw new UnsupportedOperationException(); 520 } 521 522 /** 523 * NOT SUPPORTED - Throws {@link UnsupportedOperationException} 524 */ 525 public Object getUserData(String theName) { 526 throw new UnsupportedOperationException(); 527 } 528 529 /** 530 * NOT SUPPORTED - Throws {@link UnsupportedOperationException} 531 */ 532 public void setUserData(String theName, Object theValue) { 533 throw new UnsupportedOperationException(); 534 } 535 536 537 public Location getLocation() { 538 return location; 539 } 540 541 542 public void setLocation(Location location) { 543 this.location = location; 544 } 545 546 // xhtml easy adders ----------------------------------------------- 547 public XhtmlNode h1() { 548 return addTag("h1"); 549 } 550 551 public XhtmlNode h2() { 552 return addTag("h2"); 553 } 554 555 public XhtmlNode h(int level) { 556 if (level < 1 || level > 6) { 557 throw new FHIRException("Illegal Header level "+level); 558 } 559 return addTag("h"+Integer.toString(level)); 560 } 561 562 public XhtmlNode h3() { 563 return addTag("h3"); 564 } 565 566 public XhtmlNode h4() { 567 return addTag("h4"); 568 } 569 570 public XhtmlNode table(String clss) { 571 XhtmlNode res = addTag("table"); 572 if (!Utilities.noString(clss)) 573 res.setAttribute("class", clss); 574 return res; 575 } 576 577 public XhtmlNode tr() { 578 return addTag("tr"); 579 } 580 581 public XhtmlNode th() { 582 return addTag("th"); 583 } 584 585 public XhtmlNode td() { 586 return addTag("td"); 587 } 588 589 public XhtmlNode td(String clss) { 590 return addTag("td").attribute("class", clss); 591 } 592 593 public XhtmlNode colspan(String n) { 594 return setAttribute("colspan", n); 595 } 596 597 public XhtmlNode div() { 598 return addTag("div"); 599 } 600 601 public XhtmlNode para() { 602 return addTag("p"); 603 } 604 605 public XhtmlNode pre() { 606 return addTag("pre"); 607 } 608 609 public XhtmlNode pre(String clss) { 610 return addTag("pre").setAttribute("class", clss); 611 } 612 613 public void br() { 614 addTag("br"); 615 } 616 617 public void hr() { 618 addTag("hr"); 619 } 620 621 public XhtmlNode ul() { 622 return addTag("ul"); 623 } 624 625 public XhtmlNode li() { 626 return addTag("li"); 627 } 628 629 public XhtmlNode b() { 630 return addTag("b"); 631 } 632 633 public XhtmlNode i() { 634 return addTag("i"); 635 } 636 637 public XhtmlNode tx(String cnt) { 638 return addText(cnt); 639 } 640 641 // differs from tx because it returns the owner node, not the created text 642 public XhtmlNode txN(String cnt) { 643 addText(cnt); 644 return this; 645 } 646 647 public XhtmlNode tx(int cnt) { 648 return addText(Integer.toString(cnt)); 649 } 650 651 public XhtmlNode ah(String href) { 652 return addTag("a").attribute("href", href); 653 } 654 655 public XhtmlNode ah(String href, String title) { 656 return addTag("a").attribute("href", href).attribute("title", title); 657 } 658 659 public XhtmlNode img(String src) { 660 return addTag("img").attribute("src", src); 661 } 662 663 public XhtmlNode img(String src, String title) { 664 return addTag("img").attribute("src", src).attribute("title", title); 665 } 666 667 public XhtmlNode an(String href) { 668 return an(href, " "); 669 } 670 671 public XhtmlNode an(String href, String tx) { 672 XhtmlNode a = addTag("a").attribute("name", href); 673 a.tx(tx); 674 return a; 675 } 676 677 public XhtmlNode span(String style, String title) { 678 XhtmlNode res = addTag("span"); 679 if (!Utilities.noString(style)) 680 res.attribute("style", style); 681 if (!Utilities.noString(title)) 682 res.attribute("title", title); 683 return res; 684 } 685 686 687 public XhtmlNode code(String text) { 688 return addTag("code").tx(text); 689 } 690 691 public XhtmlNode code() { 692 return addTag("code"); 693 } 694 695 696 public XhtmlNode blockquote() { 697 return addTag("blockquote"); 698 } 699 700 701 @Override 702 public String toString() { 703 switch (nodeType) { 704 case Document: 705 case Element: 706 try { 707 return new XhtmlComposer(XhtmlComposer.HTML).compose(this); 708 } catch (IOException e) { 709 return super.toString(); 710 } 711 case Text: 712 return this.content; 713 case Comment: 714 return "<!-- "+this.content+" -->"; 715 case DocType: 716 return "<? "+this.content+" />"; 717 case Instruction: 718 return "<? "+this.content+" />"; 719 } 720 return super.toString(); 721 } 722 723 724 public XhtmlNode getNextElement(XhtmlNode c) { 725 boolean f = false; 726 for (XhtmlNode n : childNodes) { 727 if (n == c) 728 f = true; 729 else if (f && n.getNodeType() == NodeType.Element) 730 return n; 731 } 732 return null; 733 } 734 735 736 public XhtmlNode notPretty() { 737 notPretty = true; 738 return this; 739 } 740 741 742 public boolean isNoPretty() { 743 return notPretty; 744 } 745 746 747 public XhtmlNode style(String style) { 748 if (hasAttribute("style")) { 749 setAttribute("style", getAttribute("style")+"; "+style); 750 } else { 751 setAttribute("style", style); 752 } 753 return this; 754 } 755 756 757 public XhtmlNode nbsp() { 758 addText(NBSP); 759 return this; 760 } 761 762 763 public XhtmlNode para(String text) { 764 XhtmlNode p = para(); 765 p.addText(text); 766 return p; 767 768 } 769 770 public XhtmlNode add(XhtmlNode n) { 771 getChildNodes().add(n); 772 return this; 773 } 774 775 776 public XhtmlNode addChildren(List<XhtmlNode> children) { 777 getChildNodes().addAll(children); 778 return this; 779 } 780 781 public XhtmlNode addChildren(XhtmlNode x) { 782 if (x != null) { 783 getChildNodes().addAll(x.getChildNodes()); 784 } 785 return this; 786 } 787 788 789 public XhtmlNode input(String name, String type, String placeholder, int size) { 790 XhtmlNode p = new XhtmlNode(NodeType.Element, "input"); 791 p.attribute("name", name); 792 p.attribute("type", type); 793 p.attribute("placeholder", placeholder); 794 p.attribute("size", Integer.toString(size)); 795 getChildNodes().add(p); 796 return p; 797 } 798 799 public XhtmlNode select(String name) { 800 XhtmlNode p = new XhtmlNode(NodeType.Element, "select"); 801 p.attribute("name", name); 802 p.attribute("size", "1"); 803 getChildNodes().add(p); 804 return p; 805 } 806 807 public XhtmlNode option(String value, String text, boolean selected) { 808 XhtmlNode p = new XhtmlNode(NodeType.Element, "option"); 809 p.attribute("value", value); 810 p.attribute("selected", Boolean.toString(selected)); 811 p.tx(text); 812 getChildNodes().add(p); 813 return p; 814 } 815 816 817 public XhtmlNode remove(XhtmlNode x) { 818 getChildNodes().remove(x); 819 return this; 820 821 } 822 823 824 public void clear() { 825 getChildNodes().clear(); 826 827 } 828 829 830 public XhtmlNode backgroundColor(String color) { 831 style("background-color: "+color); 832 return this; 833 } 834 835 836 public boolean isPara() { 837 return "p".equals(name); 838 } 839 840 841 public void markdown(String md, String source) throws IOException { 842 if (md != null) { 843 String s = new MarkDownProcessor(Dialect.COMMON_MARK).process(md, source); 844 XhtmlParser p = new XhtmlParser(); 845 XhtmlNode m; 846 try { 847 m = p.parse("<div>"+s+"</div>", "div"); 848 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 849 throw new FHIRFormatError(e.getMessage(), e); 850 } 851 getChildNodes().addAll(m.getChildNodes()); 852 } 853 } 854 855 856 public XhtmlNode sep(String separator) { 857 // if there's already text, add the separator. otherwise, we'll add it next time 858 if (!seperated) { 859 seperated = true; 860 return this; 861 } 862 return tx(separator); 863 } 864 865 866 867 868 869 870 871}