public Document nextTableRow(String atts) {

    // peek at current cell - stay with it IF
    //  + it's the first cell in the row
    //  + the current cursor points at the first child (a block)
    //  + the block pointed by cursor is empty
    Element cell = peek("table-cell", "nextTableRow() is not applicable outside enclosing table");
    if (cell.getPreviousSibling() == null
        && cursor == cell.getFirstChild()
        && !cursor.hasChildNodes()) {
      attributes((Element) cell.getParentNode(), atts);
      return this;
    }

    // pop to table
    pop("table", "nextTableRow() is not applicable outside enclosing table");
    Element table = cursor;

    // last child is already table-body?
    if (table.getLastChild().getNodeName().equals("table-body")) {
      cursor = (Element) table.getLastChild();
    } else {
      push("table-body");
    }

    // add row
    push("table-row", atts);

    // add cell
    push("table-cell", "border=" + table.getAttribute("border"));
    push("block");

    // done
    return this;
  }
  /**
   * Carries out preprocessing that makes JEuclid handle the document better.
   *
   * @param doc Document
   */
  static void preprocessForJEuclid(Document doc) {
    // underbrace and overbrace
    NodeList list = doc.getElementsByTagName("mo");
    for (int i = 0; i < list.getLength(); i++) {
      Element mo = (Element) list.item(i);
      String parentName = ((Element) mo.getParentNode()).getTagName();
      if (parentName == null) {
        continue;
      }
      if (parentName.equals("munder") && isTextChild(mo, "\ufe38")) {
        mo.setAttribute("stretchy", "true");
        mo.removeChild(mo.getFirstChild());
        mo.appendChild(doc.createTextNode("\u23df"));
      } else if (parentName.equals("mover") && isTextChild(mo, "\ufe37")) {
        mo.setAttribute("stretchy", "true");
        mo.removeChild(mo.getFirstChild());
        mo.appendChild(doc.createTextNode("\u23de"));
      }
    }

    // menclose for long division doesn't allow enough top padding. Oh, and
    // <mpadded> isn't implemented. And there isn't enough padding to left of
    // the bar either. Solve by adding an <mover> with just an <mspace> over#
    // the longdiv, contained within an mrow that adds a <mspace> before it.
    list = doc.getElementsByTagName("menclose");
    for (int i = 0; i < list.getLength(); i++) {
      Element menclose = (Element) list.item(i);
      // Only for longdiv
      if (!"longdiv".equals(menclose.getAttribute("notation"))) {
        continue;
      }
      Element mrow = doc.createElementNS(WebMathsService.NS, "mrow");
      Element mover = doc.createElementNS(WebMathsService.NS, "mover");
      Element mspace = doc.createElementNS(WebMathsService.NS, "mspace");
      Element mspaceW = doc.createElementNS(WebMathsService.NS, "mspace");
      boolean previousElement = false;
      for (Node previous = menclose.getPreviousSibling();
          previous != null;
          previous = previous.getPreviousSibling()) {
        if (previous.getNodeType() == Node.ELEMENT_NODE) {
          previousElement = true;
          break;
        }
      }
      if (previousElement) {
        mspaceW.setAttribute("width", "4px");
      }
      menclose.getParentNode().insertBefore(mrow, menclose);
      menclose.getParentNode().removeChild(menclose);
      mrow.appendChild(mspaceW);
      mrow.appendChild(mover);
      mover.appendChild(menclose);
      mover.appendChild(mspace);
    }
  }
  /** Add a list item */
  public Document nextListItem(String format) {

    // <list-block>
    //  <list-item>
    //    <list-item-label end-indent="label-end()"><block>&#x2022;</block></list-item-label>
    //    <list-item-body start-indent="body-start()">
    //       <block/>
    //    </list-item-body>
    //  </list-item>

    // check containing list-block
    Element list = peek("list-block", "nextListItem() is not applicable outside list block");

    // a list with only one item containing an empty block?
    if (list.getChildNodes().getLength() == 1
        && cursor.getFirstChild() == null
        && cursor.getPreviousSibling() == null
        && cursor.getParentNode().getLocalName().equals("list-item-body")) {
      // delete list-item and start over
      list.removeChild(list.getFirstChild());
    }

    // continue with list
    cursor = list;

    // find out what 'bullet' to use
    String label = attribute("genj:label", format);
    if (label != null) {
      // check provisional-distance-between-starts - we assume a certain 'em' per label character
      String dist = list.getAttribute("provisional-distance-between-starts");
      if (dist.endsWith("em")) {
        float len = label.length() * 0.6F;
        if (Float.parseFloat(dist.substring(0, dist.length() - 2)) < len)
          list.setAttribute("provisional-distance-between-starts", len + "em");
      }
    } else {
      label = "\u2219"; // &bullet; /u2219 works in JEditPane, &bull; \u2022 doesn't
    }

    // add new item
    push("list-item");
    push("list-item-label", "end-indent=label-end()");
    push("block");
    text(label, "");
    pop();
    pop();
    push("list-item-body", "start-indent=body-start()");
    push("block");

    return this;
  }
  public Document nextTableCell(String atts) {

    // peek at current cell - stay with it IF
    //  + it's the first cell in the row
    //  + the current cursor points at the first child (a block)
    //  + the block pointed by cursor is empty
    Element cell = peek("table-cell", "nextTableCell() is not applicable outside enclosing table");
    if (cell.getPreviousSibling() == null
        && cursor == cell.getFirstChild()
        && !cursor.hasChildNodes()) {
      attributes(cell, atts);
      // add empty content to block so another call to nextTableCell() willl actually move forward
      push("inline", "").pop();
      return this;
    }

    // peek at row
    Element row =
        peek("table-row", "nextTableCell() is not applicable outside enclosing table row");
    int cells = row.getElementsByTagName("table-cell").getLength();

    // peek at table - add new row if we have all columns already
    Element table = peek("table", "nextTableCell() is not applicable outside enclosing table");
    int cols = table.getElementsByTagName("table-column").getLength();
    if (cols > 0 && cells == cols) return nextTableRow();

    // pop to row
    pop("table-row", "nextTableCell() is not applicable outside enclosing table row");

    // 20060215 wanted to use border=inherit here but that would require table-row
    // and table-body to have border=inherit as well. table-body can't have a
    // border property in a table with border-collapse=separate (which is the only
    // model FOP supports).
    // So we're simply doing our own 'inherit' here :) Alternative would be to do
    // us border=from-nearest-specified-value() on each cell or border=inherit
    // on the table-columns and then border=from-table-column() on the cells.

    // add now
    push("table-cell", "border=" + table.getAttribute("border") + "," + atts);
    push("block");

    // done
    return this;
  }
  @Test
  public void testWrite()
      throws DITAOTException, ParserConfigurationException, SAXException, IOException {
    /*
     * the part of content of conrefpush_stub2.xml is
     * <ol>
     * 	<li id="A">A</li>
     * 	<li id="B">B</li>
     * 	<li id="C">C</li>
     * </ol>
     *
     * the part of content of conrefpush_stup.xml is
     *  <steps>
     * 	 <step conaction="pushbefore"><cmd>before</cmd></step>
     *   <step conref="conrefpush_stub2.xml#X/A" conaction="mark"/>
     *   <step conref="conrefpush_stub2.xml#X/B" conaction="mark"/>
     *	 <step conaction="pushafter"><cmd>after</cmd></step>
     *	 <step conref="conrefpush_stub2.xml#X/C" conaction="pushreplace"><cmd>replace</cmd></step>
     *	</steps>
     *
     * after conrefpush the part of conrefpush_stub2.xml should be like this
     * <ol class="- topic/ol ">
     *  <li class="- topic/li task/step ">
     *  	<ph class="- topic/ph task/cmd ">
     *  	before
     *  	</ph>
     *  </li>
     *  <li id="A" class="- topic/li ">A</li>
     *	<li id="B" class="- topic/li ">B</li>
     *	<li class="- topic/li task/step ">
     *		<ph class="- topic/ph task/cmd ">
     *		after
     *		</ph>
     *	</li>
     *	<li class="- topic/li task/step ">
     *		<ph class="- topic/ph task/cmd ">
     *		replace
     *		</ph>
     *	</li>
     * </ol>
     */
    final ConrefPushParser parser = new ConrefPushParser();
    final ConrefPushReader reader = new ConrefPushReader();

    reader.read(inputFile.getAbsolutePath());
    final Map<String, Hashtable<String, String>> pushSet = reader.getPushMap();
    final Iterator<Map.Entry<String, Hashtable<String, String>>> iter =
        pushSet.entrySet().iterator();
    if (iter.hasNext()) {
      final Map.Entry<String, Hashtable<String, String>> entry = iter.next();
      // initialize the parsed file
      FileUtils.copyFile(new File(srcDir, "conrefpush_stub2_backup.xml"), new File(entry.getKey()));
      final Content content = new ContentImpl();
      content.setValue(entry.getValue());
      parser.setContent(content);
      parser.write(entry.getKey());
      final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      final DocumentBuilder builder = factory.newDocumentBuilder();
      final Document document = builder.parse(new File(entry.getKey()));
      final Element elem = document.getDocumentElement();
      NodeList nodeList = elem.getChildNodes();
      // according to the structure, it comes to the <li> after 2 iterations.
      for (int i = 0; i < 2; i++) {
        for (int j = 0; j < nodeList.getLength(); j++) {
          if (nodeList.item(j).getNodeType() == Node.ELEMENT_NODE) {
            nodeList = nodeList.item(j).getChildNodes();
            break;
          }
        }
      }
      Element element;
      for (int i = 0; i < nodeList.getLength(); i++) {
        Node node = nodeList.item(i);
        if (node.getNodeType() == Node.ELEMENT_NODE) {
          element = (Element) node;
          if (element.getAttributes().getNamedItem("id") != null
              && element.getAttributes().getNamedItem("id").getNodeValue().equals("A")) {
            // get node of before
            node = element.getPreviousSibling();
            while (node.getNodeType() != Node.ELEMENT_NODE) {
              node = node.getPreviousSibling();
            }
            assertEquals(
                "<li class=\"- topic/li task/step \"><ph class=\"- topic/ph task/cmd \">before</ph></li>",
                nodeToString((Element) node));
          } else if (element.getAttributes().getNamedItem("id") != null
              && element.getAttributes().getNamedItem("id").getNodeValue().equals("B")) {
            // get node of after
            node = element.getNextSibling();
            while (node.getNodeType() != Node.ELEMENT_NODE) {
              node = node.getNextSibling();
            }
            assertEquals(
                "<li class=\"- topic/li task/step \"><ph class=\"- topic/ph task/cmd \">after</ph></li>",
                nodeToString((Element) node));

            // get node of replacement
            node = node.getNextSibling();
            while (node.getNodeType() != Node.ELEMENT_NODE) {
              node = node.getNextSibling();
            }
            assertEquals(
                "<li class=\"- topic/li task/step \" id=\"C\"><ph class=\"- topic/ph task/cmd \">replace</ph></li>",
                nodeToString((Element) node));
          }
        }
      }
    }
  }
  @Override
  protected void initChildren(Element eThis) throws OmException {
    Node nPrevious = eThis.getPreviousSibling();
    if (nPrevious != null && nPrevious instanceof Text) {
      String sText = ((Text) nPrevious).getData();
      if (sText.length() > 0 && Character.isWhitespace(sText.charAt(sText.length() - 1)))
        bSpaceBefore = true;
    }
    Node nAfter = eThis.getNextSibling();
    if (nAfter != null && nAfter instanceof Text) {
      String sText = ((Text) nAfter).getData();
      if (sText.length() > 0 && Character.isWhitespace(sText.charAt(0))) bSpaceAfter = true;
    }

    List<Place> lPlaces = new LinkedList<Place>();
    int iPlace = 0;
    StringBuffer sbText = new StringBuffer();
    for (Node n = eThis.getFirstChild(); n != null; n = n.getNextSibling()) {
      if (n instanceof Element) {
        Element eplace = (Element) n;
        if (!eplace.getTagName().equals("eplace"))
          throw new OmFormatException("<equation> may only contain text and <eplace> tags");
        Element[] aeChildren = XML.getChildren(eplace);
        QComponent qcChild;
        boolean bImplicit = false;
        if (aeChildren.length != 1) // Treats more than one child as inside <t>
        {
          qcChild = getQDocument().build(this, eplace, "t");
          bImplicit = true;
        } else // Treats single child as specific component (auto-sizing works)
        qcChild = getQDocument().build(this, aeChildren[0], null);
        addChild(qcChild); // Must be stored in standard child array so it
        // can be found etc.

        // See if width/height is specified
        int iWidth, iHeight;
        if (eplace.hasAttribute("width") && eplace.hasAttribute("height")) {
          try {
            iWidth = Integer.parseInt(eplace.getAttribute("width"));
            iHeight = Integer.parseInt(eplace.getAttribute("height"));
          } catch (NumberFormatException nfe) {
            throw new OmFormatException("<equation> <eplace>: width= and height= must be integers");
          }
        } else {
          Dimension d = qcChild.getApproximatePixelSize();
          if (d == null)
            throw new OmFormatException(
                "<equation> <eplace>: Except for components that support automatic "
                    + "size estimation and fixing, <eplace> must include width= and height=");
          iWidth = d.width;
          iHeight = d.height;
        }

        Place p = new Place();
        p.sID = "p" + (iPlace++);
        p.qc = qcChild;
        p.iWidth = iWidth;
        p.iHeight = iHeight;
        p.bImplicit = bImplicit;
        if (!eplace.hasAttribute("label"))
          throw new OmFormatException("<equation> <eplace>: Must include label=");
        if (eplace.hasAttribute("label")) p.sLabel = eplace.getAttribute("label");
        else p.sLabel = null;
        if (eplace.hasAttribute("for")) p.sLabelFor = eplace.getAttribute("for");
        else if (qcChild instanceof Labelable) p.sLabelFor = qcChild.getID();
        lPlaces.add(p);

        // Add in the equation format text representing the placeholder
        sbText.append("\\placeholder{" + p.sID + "}{" + p.iWidth + "," + p.iHeight + "}");
      } else if (n instanceof Text) {
        sbText.append(n.getNodeValue());
      }
    }
    sEquation = sbText.toString();
    apPlaces = lPlaces.toArray(new Place[0]);
  }
  private boolean newlineBeforeElementOpen(Element element, int depth) {
    if (hasBlankLineAbove()) {
      return false;
    }

    if (mPrefs.removeEmptyLines || depth <= 0) {
      return false;
    }

    if (isMarkupElement(element)) {
      return false;
    }

    // See if this element should be separated from the previous element.
    // This is the case if we are not compressing whitespace (checked above),
    // or if we are not immediately following a comment (in which case the
    // newline would have been added above it), or if we are not in a formatting
    // style where
    if (mStyle == XmlFormatStyle.LAYOUT) {
      // In layouts we always separate elements
      return true;
    }

    if (mStyle == XmlFormatStyle.MANIFEST
        || mStyle == XmlFormatStyle.RESOURCE
        || mStyle == XmlFormatStyle.FILE) {
      Node curr = element.getPreviousSibling();

      // <style> elements are traditionally separated unless it follows a comment
      if (TAG_STYLE.equals(element.getTagName())) {
        if (curr == null
            || curr.getNodeType() == Node.ELEMENT_NODE
            || (curr.getNodeType() == Node.TEXT_NODE
                && curr.getNodeValue().trim().isEmpty()
                && (curr.getPreviousSibling() == null
                    || curr.getPreviousSibling().getNodeType() == Node.ELEMENT_NODE))) {
          return true;
        }
      }

      // In all other styles, we separate elements if they have a different tag than
      // the previous one (but we don't insert a newline inside tags)
      while (curr != null) {
        short nodeType = curr.getNodeType();
        if (nodeType == Node.ELEMENT_NODE) {
          Element sibling = (Element) curr;
          if (!element.getTagName().equals(sibling.getTagName())) {
            return true;
          }
          break;
        } else if (nodeType == Node.TEXT_NODE) {
          String text = curr.getNodeValue();
          if (!text.trim().isEmpty()) {
            break;
          }
          // If there is just whitespace, continue looking for a previous sibling
        } else {
          // Any other previous node type, such as a comment, means we don't
          // continue looking: this element should not be separated
          break;
        }
        curr = curr.getPreviousSibling();
      }
      if (curr == null && depth <= 1) {
        // Insert new line inside tag if it's the first element inside the root tag
        return true;
      }

      return false;
    }

    return false;
  }