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}