Package org.eclipse.wst.xml.core.internal.formatter

Source Code of org.eclipse.wst.xml.core.internal.formatter.DefaultXMLPartitionFormatter

/*******************************************************************************
* Copyright (c) 2007, 2011 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     IBM Corporation - initial API and implementation
*     David Carver (STAR) - bug 297006 - String Comparison
*******************************************************************************/
package org.eclipse.wst.xml.core.internal.formatter;

import java.util.Iterator;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.xml.core.internal.Logger;
import org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMDataType;
import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNamedNodeMap;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMText;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
import org.eclipse.wst.xml.core.internal.ssemodelquery.ModelQueryAdapter;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class DefaultXMLPartitionFormatter {
  /**
   * Just a small container class that holds a DOMNode & documentRegion that
   * should represent each other.
   */
  protected class DOMRegion {
    public IDOMNode domNode;
    public IStructuredDocumentRegion documentRegion;
  }

  static private final String PRESERVE = "preserve";//$NON-NLS-1$
  static private final String COLLAPSE = "collapse";//$NON-NLS-1$
  static private final String REPLACE = "replace";//$NON-NLS-1$
  static private final String PRESERVE_QUOTED = "\"preserve\"";//$NON-NLS-1$
  static private final String XML_SPACE = "xml:space";//$NON-NLS-1$
  static private final String XSL_NAMESPACE = "http://www.w3.org/1999/XSL/Transform"; //$NON-NLS-1$
  static private final String XSL_ATTRIBUTE = "attribute"; //$NON-NLS-1$
  static private final String XSL_TEXT = "text"; //$NON-NLS-1$
  static private final String SPACE = " "; //$NON-NLS-1$
  static private final String EMPTY = ""; //$NON-NLS-1$
  static private final String PROPERTY_WHITESPACE_FACET = "org.eclipse.wst.xsd.cm.properties/whitespace"; //$NON-NLS-1$

  private XMLFormattingPreferences fPreferences = null;
  private IProgressMonitor fProgressMonitor;

  private int replaceSpaces(TextEdit textEdit, int spaceStartOffset, int availableLineWidth, String whitespaceRun) {
    StringBuffer buff = new StringBuffer(whitespaceRun);
    for(int i = 0; i < buff.length(); i++) {
      buff.setCharAt(i, ' '); //$NON-NLS-1$
    }
    String replacementString = buff.toString();
    if (!replacementString.equals(whitespaceRun)) {
      ReplaceEdit replaceEdit = new ReplaceEdit(spaceStartOffset, whitespaceRun.length(), replacementString);
      textEdit.addChild(replaceEdit);
    }
    return availableLineWidth;
  }
 
  private int collapseSpaces(TextEdit textEdit, int spaceStartOffset, int availableLineWidth, String whitespaceRun) {
    // prefer to use use existing whitespace
    int existingWhitespaceOffset = whitespaceRun.indexOf(' ');
    if (existingWhitespaceOffset > -1) {
      // delete whitespaces before and after existing whitespace
      if (existingWhitespaceOffset > 0) {
        DeleteEdit deleteEdit = new DeleteEdit(spaceStartOffset, existingWhitespaceOffset);
        textEdit.addChild(deleteEdit);
      }
      if (existingWhitespaceOffset < whitespaceRun.length() - 1) {
        int nextOffset = existingWhitespaceOffset + 1;
        DeleteEdit deleteEdit = new DeleteEdit(spaceStartOffset + nextOffset, whitespaceRun.length() - nextOffset);
        textEdit.addChild(deleteEdit);
      }
    }
    else {
      // delete all whitespace and insert new one
      // collapse whitespace by deleting whitespace
      DeleteEdit deleteEdit = new DeleteEdit(spaceStartOffset, whitespaceRun.length());
      textEdit.addChild(deleteEdit);
      // then insert one space
      InsertEdit insertEdit = new InsertEdit(spaceStartOffset, SPACE);
      textEdit.addChild(insertEdit);
    }
    // remember to account for space added
    --availableLineWidth;
    return availableLineWidth;
  }
 
  private int collapseAndIndent(TextEdit textEdit, int spaceStartOffset, int availableLineWidth, int indentLevel, String whitespaceRun, IStructuredDocumentRegion currentRegion) {
    // Need to keep blank lines, but still collapse the whitespace
    String lineDelimiters = null;
    if (!getFormattingPreferences().getClearAllBlankLines()) {
      lineDelimiters = extractLineDelimiters(whitespaceRun, currentRegion);
      String formattedLine = lineDelimiters + getIndentString(indentLevel);
      if(lineDelimiters.length() > 0 && !formattedLine.equals(whitespaceRun)) {
        textEdit.addChild(new ReplaceEdit(spaceStartOffset, whitespaceRun.length(), formattedLine));
        availableLineWidth = getFormattingPreferences().getMaxLineWidth() - indentLevel;
      }
    }
    if (lineDelimiters == null || lineDelimiters.length() == 0) {
      availableLineWidth = collapseSpaces(textEdit, spaceStartOffset, availableLineWidth, whitespaceRun);
    }
    return availableLineWidth;
  }

  private void deleteTrailingSpaces(TextEdit textEdit, ITextRegion currentTextRegion, IStructuredDocumentRegion currentDocumentRegion) {
    int textEnd = currentTextRegion.getTextEnd();
    int textEndOffset = currentDocumentRegion.getStartOffset() + textEnd;
    int difference = currentTextRegion.getEnd() - textEnd;
    DeleteEdit deleteEdit = new DeleteEdit(textEndOffset, difference);
    textEdit.addChild(deleteEdit);
  }

  public TextEdit format(IDocument document, int start, int length) {
    return format(document, start, length, new XMLFormattingPreferences());
  }

  public TextEdit format(IDocument document, int start, int length, XMLFormattingPreferences preferences) {
    TextEdit edit = null;
    if (document instanceof IStructuredDocument) {
      IStructuredModel model = StructuredModelManager.getModelManager().getModelForEdit((IStructuredDocument) document);
      if (model != null) {
        try {
          edit = format(model, start, length, preferences);
        }
        finally {
          model.releaseFromEdit();
        }
      }
    }
    return edit;
  }

  public TextEdit format(IStructuredModel model, int start, int length) {
    return format(model, start, length, new XMLFormattingPreferences());
  }

  public TextEdit format(IStructuredModel model, int start, int length, XMLFormattingPreferences preferences) {
    setFormattingPreferences(preferences);

    TextEdit edit = new MultiTextEdit();
    IStructuredDocument document = model.getStructuredDocument();
    // get initial document region
    IStructuredDocumentRegion currentRegion = document.getRegionAtCharacterOffset(start);
    if (currentRegion != null) {
      int startOffset = currentRegion.getStartOffset();

      // get initial dom node
      IndexedRegion currentIndexedRegion = model.getIndexedRegion(startOffset);
      if (currentIndexedRegion instanceof IDOMNode) {
        // set up domRegion which will contain current region to be
        // formatted
        IDOMNode currentDOMNode = (IDOMNode) currentIndexedRegion;
        DOMRegion domRegion = new DOMRegion();
        domRegion.documentRegion = currentRegion;
        domRegion.domNode = currentDOMNode;
       
        XMLFormattingConstraints parentConstraints = getRegionConstraints(currentDOMNode);
       
        /* if the whitespace strategy is declared as default, get it from the preferences */
        if(XMLFormattingConstraints.DEFAULT.equals(parentConstraints.getWhitespaceStrategy()))
          parentConstraints.setWhitespaceStrategy(preferences.getElementWhitespaceStrategy());
       
        // TODO: initialize indentLevel
        // initialize available line width
        int lineWidth = getFormattingPreferences().getMaxLineWidth();
        try {
          IRegion lineInfo = document.getLineInformationOfOffset(startOffset);
          lineWidth = lineWidth - (startOffset - lineInfo.getOffset());
        }
        catch (BadLocationException e) {
          Logger.log(Logger.WARNING_DEBUG, e.getMessage(), e);
        }
        parentConstraints.setAvailableLineWidth(lineWidth);

        // format all siblings (and their children) as long they
        // overlap with start/length
        Position formatRange = new Position(start, length);
        formatSiblings(edit, domRegion, parentConstraints, formatRange);
      }
    }
    return edit;
  }

  /**
   * Determines the formatting constraints for a specified node based on
   * its ancestors' formatting. In particular, if any ancestor node either
   * explicitly defines whitespace preservation or ignorance, that
   * whitespace strategy should be used for <code>currentNode</code> and
   * all of its descendants.
   *
   * @param currentNode the node to investigate the ancestry of to determine
   * formatting constraints
   *
   * @return formatting constraints defined by an ancestor
   */
  private XMLFormattingConstraints getRegionConstraints(IDOMNode currentNode) {
    IDOMNode iterator = currentNode;
    XMLFormattingConstraints result = new XMLFormattingConstraints();
    DOMRegion region = new DOMRegion();
    XMLFormattingConstraints parentConstraints = new XMLFormattingConstraints();
    boolean parent = true;
   
    /* Iterate through the ancestry to find if any explicit whitespace strategy has
     * been defined
     */
    while(iterator != null && iterator.getNodeType() != Node.DOCUMENT_NODE) {
      iterator = (IDOMNode) iterator.getParentNode();
      region.domNode = iterator;
      region.documentRegion = iterator.getFirstStructuredDocumentRegion();
     
      updateFormattingConstraints(null, null, result, region);
     
      /* If this is the parent of the current node, keep the constraints
       * in case no other constraints are identified
       */
      if(parent) {
        parentConstraints.copyConstraints(result);
        parent = false;
      }
     
      /* A parent who has defined a specific whitespace strategy was found */
      if(XMLFormattingConstraints.PRESERVE.equals(result.getWhitespaceStrategy()) || XMLFormattingConstraints.DEFAULT.equals(result.getWhitespaceStrategy()))
        return result;
    }
   
    return parentConstraints;
  }
//  private XMLFormattingConstraints getRegionConstraints(IDOMNode currentNode) {
//    IDOMNode iterator = (IDOMNode) currentNode.getParentNode();
//    XMLFormattingConstraints result = new XMLFormattingConstraints();
//    DOMRegion region = new DOMRegion();
//   
//    /* Iterate through the ancestry to find if any explicit whitespace strategy has
//     * been defined
//     */
//    while(iterator != null && iterator.getNodeType() != Node.DOCUMENT_NODE) {

//      region.domNode = iterator;
//      region.documentRegion = iterator.getFirstStructuredDocumentRegion();
//     
//      updateFormattingConstraints(null, null, result, region);
//     
//      /* A parent who has defined a specific whitespace strategy was found */
//      if(XMLFormattingConstraints.PRESERVE == result.getWhitespaceStrategy() || XMLFormattingConstraints.DEFAULT == result.getWhitespaceStrategy())
//        return result;
//     
//      iterator = (IDOMNode) iterator.getParentNode();
//    }
//   
//    return null;
//  }
 
  /**
   * Formats the given xml content region
   *
   * @param textEdit
   * @param formatRange
   * @param parentConstraints
   * @param currentDOMRegion
   * @param previousRegion
   */
  private void formatContent(TextEdit textEdit, Position formatRange, XMLFormattingConstraints parentConstraints, DOMRegion currentDOMRegion, IStructuredDocumentRegion previousRegion) {
    IStructuredDocumentRegion currentRegion = currentDOMRegion.documentRegion;
    String fullText = currentDOMRegion.domNode.getSource();

    // check if in preserve space mode, if so, don't touch anything but
    // make sure to update available line width
    String whitespaceMode = parentConstraints.getWhitespaceStrategy();
    if (XMLFormattingConstraints.PRESERVE.equals(whitespaceMode)) {
      int availableLineWidth = parentConstraints.getAvailableLineWidth();
      availableLineWidth = updateLineWidthWithLastLine(fullText, availableLineWidth);

      // update available line width in constraints
      parentConstraints.setAvailableLineWidth(availableLineWidth);
      // A text node can contain multiple structured document regions - sync the documentRegion
      // with the last region of the node since the text from all regions was formatted
      currentDOMRegion.documentRegion = currentDOMRegion.domNode.getLastStructuredDocumentRegion();
      return;
    }

    // if content is just whitespace and there's something after it
    // just skip over this region because region will take care of it
    boolean isAllWhitespace = ((IDOMText) currentDOMRegion.domNode).isElementContentWhitespace();
    IStructuredDocumentRegion nextDocumentRegion = null;
    if (isAllWhitespace) {
      parentConstraints.setAvailableLineWidth(fPreferences.getMaxLineWidth());
      nextDocumentRegion = currentRegion.getNext();
      if (nextDocumentRegion != null)
        return;
    }

    // special handling if text follows an entity or cdata region
    if (!XMLFormattingConstraints.COLLAPSE.equals(whitespaceMode) && previousRegion != null) {
      String previouRegionType = previousRegion.getType();
      if (DOMRegionContext.XML_ENTITY_REFERENCE.equals(previouRegionType) || DOMRegionContext.XML_CDATA_TEXT.equals(previouRegionType))
        whitespaceMode = XMLFormattingConstraints.COLLAPSE;
    }
    // also, special handling if text is before an entity or cdata region
    if (!XMLFormattingConstraints.COLLAPSE.equals(whitespaceMode)) {
      // get next document region if dont already have it
      if (nextDocumentRegion == null)
        nextDocumentRegion = currentRegion.getNext();
      if (nextDocumentRegion != null) {
        String nextRegionType = nextDocumentRegion.getType();
        if (DOMRegionContext.XML_ENTITY_REFERENCE.equals(nextRegionType) || DOMRegionContext.XML_CDATA_TEXT.equals(nextRegionType))
          whitespaceMode = XMLFormattingConstraints.COLLAPSE;
      }
    }
    final IStructuredDocumentRegion lastRegion = currentDOMRegion.domNode.getLastStructuredDocumentRegion();
    formatTextInContent(textEdit, parentConstraints, currentRegion, lastRegion != null ? lastRegion.getNext(): null, fullText, whitespaceMode);
    // A text node can contain multiple structured document regions - sync the documentRegion
    // with the last region of the node since the text from all regions was formatted
    currentDOMRegion.documentRegion = lastRegion;
  }

  private void formatEmptyStartTagWithNoAttr(TextEdit textEdit, XMLFormattingConstraints constraints, IStructuredDocumentRegion currentDocumentRegion, IStructuredDocumentRegion previousDocumentRegion, int availableLineWidth, String indentStrategy, String whitespaceStrategy, ITextRegion currentTextRegion) {
    // get preference if there should be a space or not between tag
    // name and empty tag close
    // <tagName />
    boolean oneSpaceInTagName = getFormattingPreferences().getSpaceBeforeEmptyCloseTag();

    // calculate available line width
    int tagNameLineWidth = currentTextRegion.getTextLength() + 3;
    if (oneSpaceInTagName) {
      // add one more to account for space before empty tag close
      ++tagNameLineWidth;
    }
    availableLineWidth -= tagNameLineWidth;

    if (XMLFormattingConstraints.INLINE.equals(indentStrategy)) {
      // if was inlining, need to check if out of available line
      // width
      if (availableLineWidth < 0) {
        // need to indent if possible
        int lineWidth = indentIfPossible(textEdit, constraints, currentDocumentRegion, previousDocumentRegion, whitespaceStrategy, indentStrategy, true);
        // update available line width
        if (lineWidth > 0)
          availableLineWidth = lineWidth - tagNameLineWidth;
        else
          availableLineWidth -= tagNameLineWidth;
      }
      else {
        // no need to indent
        // just make sure to delete previous whitespace if
        // needed
        if ((DOMRegionContext.XML_CONTENT.equals(previousDocumentRegion.getType())) && (previousDocumentRegion.getFullText().trim().length() == 0)) {
          availableLineWidth = collapseSpaces(textEdit, previousDocumentRegion.getStartOffset(), availableLineWidth, previousDocumentRegion.getFullText());
        }
      }
    }

    // delete any trail spaces after tag name
    int textLength = currentTextRegion.getTextLength();
    int regionLength = currentTextRegion.getLength();

    boolean thereAreSpaces = textLength < regionLength;
    if (!oneSpaceInTagName && thereAreSpaces) {
      deleteTrailingSpaces(textEdit, currentTextRegion, currentDocumentRegion);
    }
    else if(oneSpaceInTagName) {
      insertSpaceAndCollapse(textEdit, currentDocumentRegion, availableLineWidth, currentTextRegion);
    }
    constraints.setAvailableLineWidth(availableLineWidth);
  }

  /**
   * Formats an end tag
   *
   * @param textEdit
   * @param currentRegion
   * @param textRegions
   */
  private void formatEndTag(TextEdit textEdit, Position formatRange, XMLFormattingConstraints constraints, DOMRegion currentDOMRegion, IStructuredDocumentRegion previousDocumentRegion) {
    IStructuredDocumentRegion currentDocumentRegion = currentDOMRegion.documentRegion;

    String whitespaceStrategy = constraints.getWhitespaceStrategy();
    String indentStrategy = constraints.getIndentStrategy();

    // do not format space before start tag if preserving spaces
    if (whitespaceStrategy != XMLFormattingConstraints.PRESERVE) {
      // format like indent strategy says
      if (XMLFormattingConstraints.INDENT.equals(indentStrategy) || XMLFormattingConstraints.NEW_LINE.equals(indentStrategy)) {
        int availableLineWidth = indentIfPossible(textEdit, constraints, currentDocumentRegion, previousDocumentRegion, whitespaceStrategy, indentStrategy, false);
        constraints.setAvailableLineWidth(availableLineWidth);
      }
      else if ( XMLFormattingConstraints.INLINE.equals(indentStrategy)){
        IStructuredDocument doc = currentDocumentRegion.getParentDocument();
        int currentLine = doc.getLineOfOffset(currentDocumentRegion.getStartOffset());
        int prevLine = doc.getLineOfOffset(previousDocumentRegion.getStartOffset());
        if ( currentLine != prevLine){
          int availableLineWidth = indentIfPossible(textEdit, constraints, currentDocumentRegion, previousDocumentRegion, whitespaceStrategy, indentStrategy, false);
          constraints.setAvailableLineWidth(availableLineWidth);
        }
      }
    }
    // format the end tag itself
    formatWithinEndTag(textEdit, constraints, currentDocumentRegion, previousDocumentRegion);
  }

  /**
   * Formats the given region (and all its children) contained in domRegion.
   *
   * @param edit
   *            edits required to format
   * @param formatRange
   *            document range to format (only format content within this
   *            range)
   * @param parentConstraints
   * @param domRegion
   *            assumes dom node & region are not null
   * @param previousRegion
   *            could be null
   * @return Returns the last region formatted
   */
  private DOMRegion formatRegion(TextEdit edit, Position formatRange, XMLFormattingConstraints parentConstraints, DOMRegion domRegion, IStructuredDocumentRegion previousRegion) {
    IStructuredDocumentRegion currentRegion = domRegion.documentRegion;
    String regionType = currentRegion.getType();
    if (DOMRegionContext.XML_TAG_NAME.equals(regionType)) {
      ITextRegion textRegion = currentRegion.getFirstRegion();
      String textRegionType = textRegion.getType();
      if (DOMRegionContext.XML_TAG_OPEN.equals(textRegionType)) {
        domRegion = formatStartTag(edit, formatRange, parentConstraints, domRegion, previousRegion);
      }
      else if (DOMRegionContext.XML_END_TAG_OPEN.equals(textRegionType)) {
        formatEndTag(edit, formatRange, parentConstraints, domRegion, previousRegion);
      }
    }
    else if (DOMRegionContext.XML_CONTENT.equals(regionType) || domRegion.domNode.getNodeType() == Node.TEXT_NODE) {
      formatContent(edit, formatRange, parentConstraints, domRegion, previousRegion);
    }
    else if (DOMRegionContext.XML_COMMENT_TEXT.equals(regionType)) {
      formatComment(edit, formatRange, parentConstraints, domRegion, previousRegion);
    }
    else {
      // unknown, so just leave alone for now but make sure to update
      // available line width
      String fullText = currentRegion.getFullText();
      int width = updateLineWidthWithLastLine(fullText, parentConstraints.getAvailableLineWidth());
      parentConstraints.setAvailableLineWidth(width);
    }
    return domRegion;
  }

  /**
   * Formats the domRegion and all of its children and siblings
   *
   * @param edit
   * @param domRegion
   * @param parentConstraints
   * @param formatRange
   */
  private void formatSiblings(TextEdit edit, DOMRegion domRegion, XMLFormattingConstraints parentConstraints, Position formatRange) {
    IStructuredDocumentRegion previousRegion = null;
    IStructuredDocumentRegion currentRegion = domRegion.documentRegion;
    IDOMNode currentDOMNode = domRegion.domNode;
    while (currentDOMNode != null && currentRegion != null && formatRange.overlapsWith(currentRegion.getStartOffset(), currentRegion.getLength()) && (fProgressMonitor == null || !fProgressMonitor.isCanceled())) {
      domRegion.documentRegion = currentRegion;
      domRegion.domNode = currentDOMNode;

      // need to make sure current document region and current
      // dom node match up
      if (currentDOMNode.getFirstStructuredDocumentRegion().equals(currentRegion)) {
        // format this document region/node, formatRegion will
        // return the last node/region formatted
        domRegion = formatRegion(edit, formatRange, parentConstraints, domRegion, previousRegion);
      }
      else {
        // TODO: need to figure out what to do if they don't
        // match up
      }
      previousRegion = domRegion.documentRegion;
      // get the next sibling information
      if (domRegion.domNode != null)
        currentDOMNode = (IDOMNode) domRegion.domNode.getNextSibling();
      else
        currentDOMNode = null;
      currentRegion = previousRegion.getNext();
    }
  }

  /**
   * Formats a start tag
   *
   * @param textEdit
   * @param currentRegion
   * @param textRegions
   */
  private DOMRegion formatStartTag(TextEdit textEdit, Position formatRange, XMLFormattingConstraints parentConstraints, DOMRegion currentDOMRegion, IStructuredDocumentRegion previousDocumentRegion) {
    // determine proper indent by referring to parent constraints,
    // previous node, and current node
    IStructuredDocumentRegion currentDocumentRegion = currentDOMRegion.documentRegion;
    IDOMNode currentDOMNode = currentDOMRegion.domNode;

    // create a constraint for this tag
    XMLFormattingConstraints thisConstraints = new XMLFormattingConstraints();
    XMLFormattingConstraints childrenConstraints = new XMLFormattingConstraints();
    updateFormattingConstraints(parentConstraints, thisConstraints, childrenConstraints, currentDOMRegion);

    if(XMLFormattingConstraints.DEFAULT.equals(childrenConstraints.getWhitespaceStrategy()))
      childrenConstraints.setWhitespaceStrategy((new XMLFormattingPreferences()).getElementWhitespaceStrategy());
     
    String whitespaceStrategy = thisConstraints.getWhitespaceStrategy();
    String indentStrategy = thisConstraints.getIndentStrategy();
    int availableLineWidth = thisConstraints.getAvailableLineWidth();

    // format space before start tag
    // do not format space before start tag if preserving spaces
    if (!XMLFormattingConstraints.PRESERVE.equals(whitespaceStrategy)) {
      // format like indent strategy says
      if (XMLFormattingConstraints.INDENT.equals(indentStrategy) || XMLFormattingConstraints.NEW_LINE.equals(indentStrategy)) {
        availableLineWidth = indentIfPossible(textEdit, thisConstraints, currentDocumentRegion, previousDocumentRegion, whitespaceStrategy, indentStrategy, true, true);
        if (availableLineWidth > 0)
          thisConstraints.setAvailableLineWidth(availableLineWidth);
      }
    }
    // format the start tag itself
    boolean tagEnded = formatWithinTag(textEdit, thisConstraints, currentDocumentRegion, previousDocumentRegion);

    // format children
    if (!tagEnded) {
      // update childConstraints with thisConstraint's indentLevel &
      // availableLineWidth
      childrenConstraints.setIndentLevel(thisConstraints.getIndentLevel());
      childrenConstraints.setAvailableLineWidth(thisConstraints.getAvailableLineWidth());

      previousDocumentRegion = currentDocumentRegion;
      IDOMNode childDOMNode = (IDOMNode) currentDOMNode.getFirstChild();
      IStructuredDocumentRegion nextRegion = currentDocumentRegion.getNext();
      boolean passedFormatRange = false;
      // as long as there is one child
      if (childDOMNode != null && nextRegion != null) {
        while (childDOMNode != null && nextRegion != null && !passedFormatRange && (fProgressMonitor == null || !fProgressMonitor.isCanceled())) {
          DOMRegion childDOMRegion = new DOMRegion();
          childDOMRegion.documentRegion = nextRegion;
          childDOMRegion.domNode = childDOMNode;
          if (nextRegion.equals(childDOMNode.getFirstStructuredDocumentRegion())) {
            // format children. pass in child constraints
            childDOMRegion = formatRegion(textEdit, formatRange, childrenConstraints, childDOMRegion, previousDocumentRegion);
          }
          else {
            // TODO: what happens if they dont match up?
          }

          // update childDOMRegion with next dom/region node
          if (childDOMRegion.domNode != null) {
            childDOMNode = (IDOMNode) childDOMRegion.domNode.getNextSibling();
          }
          else {
            childDOMNode = null;
          }
          previousDocumentRegion = childDOMRegion.documentRegion;
          nextRegion = previousDocumentRegion.getNext();
          if (nextRegion != null)
            passedFormatRange = !formatRange.overlapsWith(nextRegion.getStartOffset(), nextRegion.getLength());
        }
      }
      else {
        // there were no children, so keep end tag inlined
        childrenConstraints.setWhitespaceStrategy(XMLFormattingConstraints.COLLAPSE);
        childrenConstraints.setIndentStrategy(XMLFormattingConstraints.INLINE);
      }

      if (!passedFormatRange) {
        // update the dom region with the last formatted region/dom
        // node should be end tag and this tag's DOMNode
        currentDOMRegion.documentRegion = nextRegion;
        currentDOMRegion.domNode = currentDOMNode;

        // end tag's indent level should be same as start tag's
        childrenConstraints.setIndentLevel(thisConstraints.getIndentLevel());
        // format end tag
        boolean formatEndTag = false;
        if (nextRegion != null && currentDOMNode != null) {
          ITextRegionList rs = nextRegion.getRegions();
          if (rs.size() > 1) {
            ITextRegion r = rs.get(0);
            if (r != null && DOMRegionContext.XML_END_TAG_OPEN.equals(r.getType())) {
              r = rs.get(1);
              if (r != null && DOMRegionContext.XML_TAG_NAME.equals(r.getType())) {
                String tagName = nextRegion.getText(r);
                if (tagName != null && tagName.equals(currentDOMNode.getNodeName()))
                  formatEndTag = true;
              }
            }

          }
        }
        if (formatEndTag)
          formatEndTag(textEdit, formatRange, childrenConstraints, currentDOMRegion, previousDocumentRegion);
        else {
          // missing end tag so return last formatted document
          // region
          currentDOMRegion.documentRegion = previousDocumentRegion;
        }
      }
      else {
        // passed format range before could finish, so update dom
        // region to last known formatted region
        currentDOMRegion.documentRegion = nextRegion;
        currentDOMRegion.domNode = childDOMNode;
      }

      // update parent constraint since this is what is passed back
      parentConstraints.setAvailableLineWidth(childrenConstraints.getAvailableLineWidth());
    }
    else {
      // update available line width
      parentConstraints.setAvailableLineWidth(thisConstraints.getAvailableLineWidth());
    }
    return currentDOMRegion;
  }

  private void formatStartTagWithNoAttr(TextEdit textEdit, XMLFormattingConstraints constraints, IStructuredDocumentRegion currentDocumentRegion, IStructuredDocumentRegion previousDocumentRegion, int availableLineWidth, String indentStrategy, String whitespaceStrategy, ITextRegion currentTextRegion) {
    // calculate available line width
    int tagNameLineWidth = currentTextRegion.getTextLength() + 2;
    availableLineWidth -= tagNameLineWidth;

    if (XMLFormattingConstraints.INLINE.equals(indentStrategy)) {
      // if was inlining, need to check if out of available line
      // width
      if (availableLineWidth < 0) {
        // need to indent if possible
        int lineWidth = indentIfPossible(textEdit, constraints, currentDocumentRegion, previousDocumentRegion, whitespaceStrategy, indentStrategy, true);
        // update available line width
        if (lineWidth > 0)
          availableLineWidth = lineWidth - tagNameLineWidth;
        else
          availableLineWidth -= tagNameLineWidth;
      }
      else {
        // no need to indent
        // just make sure to delete previous whitespace if
        // needed
        if (previousDocumentRegion != null) {
          if (DOMRegionContext.XML_CONTENT.equals(previousDocumentRegion.getType())) {
            String previousDocumentRegionText = previousDocumentRegion.getFullText();
            if (previousDocumentRegionText.trim().length() == 0) {
              availableLineWidth = collapseSpaces(textEdit, previousDocumentRegion.getStartOffset(), availableLineWidth, previousDocumentRegionText);
            }
          }
        }
      }
    }

    // delete any trail spaces after tag name
    if (currentTextRegion.getTextLength() < currentTextRegion.getLength()) {
      deleteTrailingSpaces(textEdit, currentTextRegion, currentDocumentRegion);
    }
    constraints.setAvailableLineWidth(availableLineWidth);
  }

  /**
   * Format the text in xml content
   *
   * @param textEdit
   * @param parentConstraints
   * @param currentRegion
   * @param fullText
   * @param whitespaceMode
   */
  private void formatTextInContent(TextEdit textEdit, XMLFormattingConstraints parentConstraints, IStructuredDocumentRegion currentRegion, IStructuredDocumentRegion nextRegion, String fullText, String whitespaceMode) {
    int availableLineWidth = parentConstraints.getAvailableLineWidth();

    // determine indentation
    boolean forceInitialIndent = false;
    int indentLevel = parentConstraints.getIndentLevel() + 1;
    String indentMode = parentConstraints.getIndentStrategy();
    if (XMLFormattingConstraints.INDENT.equals(indentMode)) {
      forceInitialIndent = true;
    }
    if (XMLFormattingConstraints.NEW_LINE.equals(indentMode)) {
      indentLevel = parentConstraints.getIndentLevel();
      forceInitialIndent = true;
    }

    int fullTextOffset = 0;
    char[] fullTextArray = fullText.toCharArray();
    while (fullTextOffset < fullTextArray.length) {
      // gather all whitespaces
      String whitespaceRun = getCharacterRun(fullTextArray, fullTextOffset, true);
      if (whitespaceRun.length() > 0) {
        // offset where whitespace starts
        int whitespaceStart = fullTextOffset;
        // update current offset in fullText
        fullTextOffset += whitespaceRun.length();

        // gather following word
        String characterRun = getCharacterRun(fullTextArray, fullTextOffset, false);
        int characterRunLength = characterRun.length();
        if (characterRunLength > 0) {
          // indent if word is too long or forcing initial
          // indent
          availableLineWidth -= characterRunLength;
          // offset where indent/collapse will happen
          int startOffset = currentRegion.getStartOffset() + whitespaceStart;
          if (forceInitialIndent || (availableLineWidth <= 0)) {
            // indent if not already indented
            availableLineWidth = indentIfNotAlreadyIndented(textEdit, currentRegion, indentLevel, startOffset, whitespaceRun);
            // remember to subtract word length
            availableLineWidth -= characterRunLength;
            forceInitialIndent = false; // initial indent done
          }
          else {
            // just collapse spaces, but adjust for any indenting that may result from preserving line delimiters
            if (whitespaceStart == 0 && XMLFormattingConstraints.IGNOREANDTRIM.equals(whitespaceMode)) {
              // if ignore, trim
              DeleteEdit deleteTrailing = new DeleteEdit(startOffset, whitespaceRun.length());
              textEdit.addChild(deleteTrailing);
            }
            else if(XMLFormattingConstraints.REPLACE.equals(whitespaceMode))
              availableLineWidth = replaceSpaces(textEdit, startOffset, availableLineWidth, whitespaceRun);
            else
              availableLineWidth = collapseAndIndent(textEdit, startOffset, availableLineWidth, indentLevel, whitespaceRun, currentRegion);
          }

          fullTextOffset += characterRunLength;
        }
        else {
          // handle trailing whitespace
          int whitespaceOffset = currentRegion.getStartOffset() + whitespaceStart;
          if (XMLFormattingConstraints.REPLACE.equals(whitespaceMode))
            availableLineWidth = replaceSpaces(textEdit, whitespaceOffset, availableLineWidth, whitespaceRun);
          else if (XMLFormattingConstraints.IGNOREANDTRIM.equals(whitespaceMode)) {
            // always trim
            DeleteEdit deleteTrailing = new DeleteEdit(whitespaceOffset, whitespaceRun.length());
            textEdit.addChild(deleteTrailing);
          }
          else if(getFormattingPreferences().getClearAllBlankLines()) {
            if (!nextRegionHandlesTrailingWhitespace(nextRegion)) {
              if (XMLFormattingConstraints.IGNORE.equals(whitespaceMode)) {
                // if ignore, trim
                DeleteEdit deleteTrailing = new DeleteEdit(whitespaceOffset, whitespaceRun.length());
                textEdit.addChild(deleteTrailing);
              }
              else {
                // if collapse, leave a space. but what if end up
                // wanting to add indent? then need to delete space
                // added and add indent instead
                availableLineWidth = collapseSpaces(textEdit, whitespaceOffset, availableLineWidth, whitespaceRun);
              }
            }
          }
        }
      }
      else {
        // gather word
        String characterRun = getCharacterRun(fullTextArray, fullTextOffset, false);
        int characterRunLength = characterRun.length();
        if (characterRunLength > 0) {
          // indent if word is too long or forcing initial
          // indent
          // [243091] - characterRunLength should only be subtracted once or text formatting wraps prematurely
          // availableLineWidth = availableLineWidth - characterRunLength;
          if ((XMLFormattingConstraints.IGNORE.equals(whitespaceMode) || XMLFormattingConstraints.IGNOREANDTRIM.equals(whitespaceMode)) && (forceInitialIndent || (availableLineWidth <= 0))) {
            // indent if not already indented
            availableLineWidth = indentIfNotAlreadyIndented(textEdit, currentRegion, indentLevel, currentRegion.getStartOffset(), whitespaceRun);
            // remember to subtract word length
            availableLineWidth -= characterRunLength;
            forceInitialIndent = false; // initial indent done
          }
          else {
            // just collapse spaces
            availableLineWidth -= characterRunLength;
          }

          fullTextOffset += characterRunLength;
        }
      }
    }
    // update available line width
    parentConstraints.setAvailableLineWidth(availableLineWidth);
  }

  private boolean nextRegionHandlesTrailingWhitespace(IStructuredDocumentRegion region) {
    if (region == null)
      return false;
    final String type = region.getType();
    if (type.equals(DOMRegionContext.XML_TAG_NAME)) {
      return DOMRegionContext.XML_TAG_OPEN.equals(region.getFirstRegion().getType());
    }
    return DOMRegionContext.XML_COMMENT_TEXT.equals(type);
  }

  private void formatWithinEndTag(TextEdit textEdit, XMLFormattingConstraints constraints, IStructuredDocumentRegion currentDocumentRegion, IStructuredDocumentRegion previousDocumentRegion) {
    String indentStrategy = constraints.getIndentStrategy();
    String whitespaceStrategy = constraints.getWhitespaceStrategy();
    int availableLineWidth = constraints.getAvailableLineWidth();
    ITextRegionList textRegions = currentDocumentRegion.getRegions();
    int currentNumberOfRegions = currentDocumentRegion.getNumberOfRegions();
    int currentTextRegionIndex = 1;

    ITextRegion currentTextRegion = textRegions.get(currentTextRegionIndex);
    String currentType = currentTextRegion.getType();
    // tag name should always be the first text region
    if (DOMRegionContext.XML_TAG_NAME.equals(currentType) && currentTextRegionIndex < currentNumberOfRegions - 1) {
      ITextRegion nextTextRegion = textRegions.get(currentTextRegionIndex + 1);
      // Bug 221279 - Some non well-formed documents will not contribute a next region
      if (nextTextRegion != null && DOMRegionContext.XML_TAG_CLOSE.equals(nextTextRegion.getType())) {
        // calculate available line width
        int tagNameLineWidth = currentTextRegion.getTextLength() + 3;
        availableLineWidth -= tagNameLineWidth;

        if (XMLFormattingConstraints.INLINE.equals(indentStrategy)) {
          // if was inlining, need to check if out of available line
          // width - Whitespace may have been corrected in the text content
          if (availableLineWidth < 0 && XMLFormattingConstraints.IGNORE.equals(whitespaceStrategy)) {
            // need to deindent if possible
            int lineWidth = indentIfPossible(textEdit, constraints, currentDocumentRegion, previousDocumentRegion, whitespaceStrategy, indentStrategy, false);
            // update available line width
            if (lineWidth > 0)
              availableLineWidth = lineWidth - tagNameLineWidth;
          }
          else {
            // no need to indent
            // just make sure to delete previous whitespace if
            // needed
            if (previousDocumentRegion != null) {
              if (DOMRegionContext.XML_CONTENT.equals(previousDocumentRegion.getType())) {
                String previousDocumentRegionText = previousDocumentRegion.getFullText();
                if (previousDocumentRegionText.trim().length() == 0) {
                  availableLineWidth = collapseSpaces(textEdit, previousDocumentRegion.getStartOffset(), availableLineWidth, previousDocumentRegionText);
                }
              }
            }
          }
        }
        // delete any trail spaces after tag name
        if (currentTextRegion.getTextLength() < currentTextRegion.getLength()) {
          deleteTrailingSpaces(textEdit, currentTextRegion, currentDocumentRegion);
        }
      }
    }
    else {
      // end tag has unexpected stuff, so just leave it alone
    }
    constraints.setAvailableLineWidth(availableLineWidth);
  }

  /**
   * Formats the contents within a tag like tag name and attributes
   *
   * @param textEdit
   * @param currentDocumentRegion
   * @param textRegions
   *            contains at least 3 regions
   * @return true if tag was ended, false otherwise
   */
  private boolean formatWithinTag(TextEdit textEdit, XMLFormattingConstraints constraints, IStructuredDocumentRegion currentDocumentRegion, IStructuredDocumentRegion previousDocumentRegion) {
    int availableLineWidth = constraints.getAvailableLineWidth();
    String indentStrategy = constraints.getIndentStrategy();
    String whitespaceStrategy = constraints.getWhitespaceStrategy();
    int indentLevel = constraints.getIndentLevel();
    ITextRegionList textRegions = currentDocumentRegion.getRegions();
    int currentTextRegionIndex = 1;

    ITextRegion currentTextRegion = textRegions.get(currentTextRegionIndex);
    String currentType = currentTextRegion.getType();
    // tag name should always be the first text region
    if (DOMRegionContext.XML_TAG_NAME.equals(currentType)) {
      ITextRegion nextTextRegion = textRegions.get(currentTextRegionIndex + 1);
      String nextType = (nextTextRegion != null) ? nextTextRegion.getType() : null;
      if (DOMRegionContext.XML_TAG_CLOSE.equals(nextType)) {
        // already at tag close
        formatStartTagWithNoAttr(textEdit, constraints, currentDocumentRegion, previousDocumentRegion, availableLineWidth, indentStrategy, whitespaceStrategy, currentTextRegion);
        return false;
      }
      else if (DOMRegionContext.XML_EMPTY_TAG_CLOSE.equals(nextType)) {
        // already at empty tag close
        formatEmptyStartTagWithNoAttr(textEdit, constraints, currentDocumentRegion, previousDocumentRegion, availableLineWidth, indentStrategy, whitespaceStrategy, currentTextRegion);
        return true;
      }
      else {
        availableLineWidth -= (currentTextRegion.getTextLength() + 2);
        boolean alignFinalBracket = getFormattingPreferences().getAlignFinalBracket();
        boolean oneSpaceInTagName = getFormattingPreferences().getSpaceBeforeEmptyCloseTag();
        boolean indentMultipleAttribute = getFormattingPreferences().getIndentMultipleAttributes();
        // indicates if tag spanned more than one line
        boolean spanMoreThan1Line = false;
        // indicates if all attributes should be indented
        boolean indentAllAttributes = false;
        if (indentMultipleAttribute) {
          int attributesCount = 0;
          int i = 2;
          final int size = textRegions.size();
          while (i < size && attributesCount < 2) {
            if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(textRegions.get(i).getType())) {
              ++attributesCount;
            }
            i++;
          }
          indentAllAttributes = (attributesCount > 1);
        }

        while ((currentTextRegionIndex + 1) < textRegions.size()) {
          nextTextRegion = textRegions.get(currentTextRegionIndex + 1);
          nextType = nextTextRegion.getType();
          if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(nextType)) {
            boolean indentAttribute = indentAllAttributes;
            if (!indentAttribute)
              indentAttribute = shouldIndentBeforeAttribute(constraints, textRegions, availableLineWidth, currentTextRegionIndex, currentTextRegion, nextTextRegion);
            if (indentAttribute) {
              availableLineWidth = indentIfNotAlreadyIndented(textEdit, indentLevel + 1, currentDocumentRegion, currentTextRegion);
              spanMoreThan1Line = true;
            }
            else {
              // otherwise, insertSpaceAndCollapse
              insertSpaceAndCollapse(textEdit, currentDocumentRegion, availableLineWidth, currentTextRegion);
              // update available line width
              availableLineWidth -= (currentTextRegion.getTextLength() + 1);
            }
          }
          else if (DOMRegionContext.XML_TAG_CLOSE.equals(nextType)) {
            // if need to align bracket on next line, indent
            if (alignFinalBracket && spanMoreThan1Line) {
              availableLineWidth = indentIfNotAlreadyIndented(textEdit, indentLevel, currentDocumentRegion, currentTextRegion);
              --availableLineWidth; // for tag close itself
            }
            else {
              // otherwise, just delete space before tag close
              if (currentTextRegion.getTextLength() < currentTextRegion.getLength()) {
                deleteTrailingSpaces(textEdit, currentTextRegion, currentDocumentRegion);
                availableLineWidth -= (currentTextRegion.getTextLength() + 1);
              }
            }
            // update line width
            constraints.setAvailableLineWidth(availableLineWidth);
            return false;
          }
          else if (DOMRegionContext.XML_EMPTY_TAG_CLOSE.equals(nextType)) {
            int textLength = currentTextRegion.getTextLength();
            int regionLength = currentTextRegion.getLength();

            boolean thereAreSpaces = textLength < regionLength;
            if (!oneSpaceInTagName && thereAreSpaces) {
              // delete any trail spaces after tag name
              deleteTrailingSpaces(textEdit, currentTextRegion, currentDocumentRegion);
              availableLineWidth -= (currentTextRegion.getTextLength() + 2);
            }
            // insert a space and collapse ONLY IF it's specified
            else if (oneSpaceInTagName) {
              insertSpaceAndCollapse(textEdit, currentDocumentRegion, availableLineWidth, currentTextRegion);
              availableLineWidth -= (currentTextRegion.getTextLength() + 3);
            }
            // update line width
            constraints.setAvailableLineWidth(availableLineWidth);
            return true;
          }
          else {
            if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(currentType) && DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS.equals(nextType)) {
              if (currentTextRegion.getTextLength() < currentTextRegion.getLength()) {
                deleteTrailingSpaces(textEdit, currentTextRegion, currentDocumentRegion);
              }
              // update available width
              availableLineWidth -= currentTextRegion.getTextLength();
            }
            else if (DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS.equals(currentType) && DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(nextType)) {
              if (currentTextRegion.getTextLength() < currentTextRegion.getLength()) {
                deleteTrailingSpaces(textEdit, currentTextRegion, currentDocumentRegion);
              }
              // update available width
              availableLineWidth -= currentTextRegion.getTextLength();
            }
            else {
              // otherwise, insertSpaceAndCollapse
              insertSpaceAndCollapse(textEdit, currentDocumentRegion, availableLineWidth, currentTextRegion);
              // update available line width
              availableLineWidth -= (currentTextRegion.getTextLength() + 1);
            }
          }
          currentTextRegion = nextTextRegion;
          currentType = nextType;
          ++currentTextRegionIndex;
        }
      }
    }
    // update line width
    constraints.setAvailableLineWidth(availableLineWidth);
    return false;
  }
 
  /**
   * Format an XML comment structured document region.
   */
  private void formatComment(TextEdit textEdit, Position formatRange, XMLFormattingConstraints parentConstraints, DOMRegion currentDOMRegion, IStructuredDocumentRegion previousRegion) {
    IStructuredDocumentRegion currentRegion = currentDOMRegion.documentRegion;
    int lineWidth = parentConstraints.getAvailableLineWidth() - currentRegion.getFullText().length();
    // Don't format if we're not exceeding the available line width, or if the whitespace
    // strategy is to preserve whitespace - But update line width.
    if(currentRegion == null ||  XMLFormattingConstraints.PRESERVE.equals(parentConstraints.getWhitespaceStrategy()) || !fPreferences.getFormatCommentText()) {
      parentConstraints.setAvailableLineWidth(lineWidth);
      return;
    }

    Iterator it = currentRegion.getRegions().iterator();
    ITextRegion previous = null;
    if (previousRegion == null)
      previousRegion = currentRegion.getPrevious();
    // Iterate over each text region of the comment
    Node parent = currentDOMRegion.domNode.getParentNode();
    while(it.hasNext()) {
      ITextRegion text = (ITextRegion) it.next();
      String type = text.getType();
      if (type == DOMRegionContext.XML_COMMENT_OPEN) {
        int indentLevel = (parent != null && parent.getNodeType() == Node.DOCUMENT_NODE) ? 0 : 1;
        int width = formatCommentStart(textEdit, parentConstraints, indentLevel, currentRegion, previousRegion, text);
        parentConstraints.setAvailableLineWidth(width);
      }
      else if (type == DOMRegionContext.XML_COMMENT_TEXT) {
        int indentLevel = (parent != null && parent.getNodeType() == Node.DOCUMENT_NODE) ? -1 : parentConstraints.getIndentLevel();
        formatCommentContent(textEdit, parentConstraints, indentLevel, currentRegion, previous, text);
      }
      previous = text;
    }
  }

  private void formatCommentContent(TextEdit textEdit, XMLFormattingConstraints parentConstraints, int indentLevel, IStructuredDocumentRegion currentRegion, ITextRegion previous, ITextRegion region) {
    int lineWidth = parentConstraints.getAvailableLineWidth() - currentRegion.getFullText(previous).length();
    // If there's more text than line width available, format
    String text = currentRegion.getFullText(region);
    compressContent(textEdit, currentRegion, currentRegion.getStartOffset(region), indentLevel + 1, lineWidth, text);
  }

  private void compressContent(TextEdit textEdit, IStructuredDocumentRegion region, int startOffset, int indentLevel, int lineWidth, String text) {
    int length = text.length();
    int start = 0, end = 0;
    char c = 0;
    int resultLength = 0;
    boolean joinLines = fPreferences.getJoinCommentLines();
    boolean onOwnLine = false;

    String indent = getIndentString(indentLevel + 1);

    for (int i = 0; i < length; i++) {
      c = text.charAt(i);
      // Compress whitespace unless its a line delimiter and formatting does not permit joining lines
      if (Character.isWhitespace(c)) {
        if ((c != '\r' && c!= '\n') || joinLines) {
          // Just came off of a word
          if (start == end) {
            start = end = i;
          }
          end++;
          resultLength++;
        }
        else {
          // correct the indent of this line
          lineWidth = fPreferences.getMaxLineWidth();
          resultLength = 0;
          onOwnLine = true;

          // Compress any whitespace before the line delimiter
          if (start != end) {
            int replaceLength = end - start;
            textEdit.addChild(new ReplaceEdit(start + startOffset, replaceLength, EMPTY));
            start = end = i;
          }
        }
      }
      else {
        // Transitioned to a new word
        if (start != end) {
          int replaceLength = end - start;
          if (onOwnLine) {
            // If content is on its own line, replace leading whitespace with proper indent
            textEdit.addChild(new ReplaceEdit(start + startOffset, replaceLength, indent));
            resultLength -= (replaceLength - indent.length());
            onOwnLine = false;
          }
          else if (!(replaceLength == 1 && text.charAt(start) == ' ')) {
            textEdit.addChild(new ReplaceEdit(start + startOffset, replaceLength, SPACE));
            resultLength -= (replaceLength - 1);
          }
          start = end = i;
          // Make sure the word starts on a new line
          if (resultLength > lineWidth) {
            lineWidth = fPreferences.getMaxLineWidth();
            resultLength = 0;
            textEdit.addChild(new InsertEdit(start + startOffset, getLineDelimiter(region) + indent));
          }
        }
        // Word is immediately after line delimiters, indent appropriately
        if (onOwnLine) {
          textEdit.addChild(new InsertEdit(i + startOffset, indent));
          onOwnLine = false;
        }
        resultLength++;
      }
    }

    // Clean up any dangling whitespace
    int replaceLength = end - start;
    indent = getIndentString(indentLevel);
    if (replaceLength == 0) { // No trailing whitespace
      textEdit.addChild(new InsertEdit(length + startOffset, (onOwnLine) ? indent : SPACE));
    }
    else {
      String whitespace = text.substring(start);
      String replacement = (onOwnLine) ? indent : SPACE;
      if (!whitespace.equals(replacement)) {
        textEdit.addChild(new ReplaceEdit(start + startOffset, replaceLength, replacement));
      }
    }
  }

  private int formatCommentStart(TextEdit textEdit, XMLFormattingConstraints parentConstraints, int indentLevel, IStructuredDocumentRegion currentRegion, IStructuredDocumentRegion previousRegion, ITextRegion region) {
    int lineWidth = parentConstraints.getAvailableLineWidth();
    if (previousRegion!=null&&(DOMRegionContext.XML_CONTENT.equals(previousRegion.getType()))) {
      String previousText = previousRegion.getFullText();
      String trailingWhitespace = getTrailingWhitespace(previousText);
      String delimiters = extractLineDelimiters(trailingWhitespace, previousRegion);
      if (delimiters != null && delimiters.length() > 0){// && previousText.length() == trailingWhitespace.length()) {
        // Format the comment if it's on a new line
        int offset = previousRegion.getEnd() - trailingWhitespace.length();
        lineWidth = indentIfNotAlreadyIndented(textEdit, currentRegion, parentConstraints.getIndentLevel() + indentLevel, offset, trailingWhitespace);
      }
    }
    return lineWidth;
  }

  /**
   * Returns either a String of whitespace or characters depending on
   * forWhitespace
   *
   * @param fullTextArray
   *            the text array to look in
   * @param textOffset
   *            the start offset to start searching
   * @param forWhitespace
   *            true if should return whitespaces, false otherwise
   * @return a String of either all whitespace or all characters. Never
   *         returns null
   */
  private String getCharacterRun(char[] fullTextArray, int textOffset, boolean forWhitespace) {
    StringBuffer characterRun = new StringBuffer();
    boolean nonCharacterFound = false;
    while (textOffset < fullTextArray.length && !nonCharacterFound) {
      char c = fullTextArray[textOffset];
      boolean isWhitespace = Character.isWhitespace(c);
      if ((forWhitespace && isWhitespace) || (!forWhitespace && !isWhitespace))
        characterRun.append(c);
      else
        nonCharacterFound = true;
      ++textOffset;
    }
    return characterRun.toString();
  }
 
  private String getTrailingWhitespace(String text) {
    StringBuffer whitespaceRun = new StringBuffer();
    int index = text.length() - 1;
    while(index >= 0) {
      char c = text.charAt(index--);
      if (Character.isWhitespace(c))
        whitespaceRun.insert(0, c);
      else
        break;
    }
    return whitespaceRun.toString();
  }

  private String getIndentString(int indentLevel) {
    StringBuffer indentString = new StringBuffer();
    String indent = getFormattingPreferences().getOneIndent();
    for (int i = 0; i < indentLevel; ++i) {
      indentString.append(indent);
    }
    return indentString.toString();
  }

  protected XMLFormattingPreferences getFormattingPreferences() {
    if (fPreferences == null)
      fPreferences = new XMLFormattingPreferences();
    return fPreferences;
  }

  protected void setFormattingPreferences(XMLFormattingPreferences preferences) {
    fPreferences = preferences;
  }

  /**
   * Indent if whitespaceRun does not already contain an indent
   *
   * @param textEdit
   * @param indentLevel
   * @param indentStartOffset
   * @param maxAvailableLineWidth
   * @param whitespaceRun
   * @return new available line width up to where indented
   */
  private int indentIfNotAlreadyIndented(TextEdit textEdit, IStructuredDocumentRegion currentRegion, int indentLevel, int indentStartOffset, String whitespaceRun) {
    int maxAvailableLineWidth = getFormattingPreferences().getMaxLineWidth();

    int availableLineWidth;
    String indentString = getIndentString(indentLevel);
    String lineDelimiter = getLineDelimiter(currentRegion);
    String newLineAndIndent = lineDelimiter + indentString;
   
    TextEdit indentation = null;
   
    // if not already correctly indented
    if (!newLineAndIndent.equals(whitespaceRun)) {
      if (getFormattingPreferences().getClearAllBlankLines()) {
        if (whitespaceRun != null) {
          // replace existing whitespace run
          indentation = new ReplaceEdit(indentStartOffset, whitespaceRun.length(), newLineAndIndent);
        }
        else {
          // just insert correct indent
          indentation = new InsertEdit(indentStartOffset, newLineAndIndent);
        }
      }
      // Keep the empty lines
      else {
        // just insert correct indent
        if(whitespaceRun == null)
          indentation = new InsertEdit(indentStartOffset, newLineAndIndent);
        // Need to preserve the number of empty lines, but still indent on the current line properly
        else {
          String existingDelimiters = extractLineDelimiters(whitespaceRun, currentRegion);
          if(existingDelimiters != null && existingDelimiters.length() > 0) {
            String formatted = existingDelimiters + indentString;
            // Don't perform a replace if the formatted string is the same as the existing whitespaceRun
            if(!formatted.equals(whitespaceRun))
              indentation = new ReplaceEdit(indentStartOffset, whitespaceRun.length(), formatted);
          }
          // No blank lines to preserve - correct the indent
          else
            indentation = new ReplaceEdit(indentStartOffset, whitespaceRun.length(), newLineAndIndent);
        }
      }
    }
   
    if(indentation != null)
      textEdit.addChild(indentation);
    // update line width
    availableLineWidth = maxAvailableLineWidth - indentString.length();
    return availableLineWidth;
  }

  private int indentIfNotAlreadyIndented(TextEdit textEdit, int indentLevel, IStructuredDocumentRegion currentDocumentRegion, ITextRegion currentTextRegion) {
    // indent if not already indented
    int textLength = currentTextRegion.getTextLength();
    int regionLength = currentTextRegion.getLength();
    int indentStartOffset = currentDocumentRegion.getTextEndOffset(currentTextRegion);
    String fullText = currentDocumentRegion.getFullText(currentTextRegion);
    String whitespaceRun = fullText.substring(textLength, regionLength);

    // update line width
    int availableLineWidth = indentIfNotAlreadyIndented(textEdit, currentDocumentRegion, indentLevel, indentStartOffset, whitespaceRun);
    return availableLineWidth;
  }

  private int indentIfPossible(TextEdit textEdit, XMLFormattingConstraints thisConstraints, IStructuredDocumentRegion currentDocumentRegion, IStructuredDocumentRegion previousDocumentRegion, String whitespaceStrategy, String indentStrategy, boolean addIndent) {
    return indentIfPossible(textEdit, thisConstraints, currentDocumentRegion, previousDocumentRegion, whitespaceStrategy, indentStrategy, addIndent, !getFormattingPreferences().getClearAllBlankLines());
  }

  private int indentIfPossible(TextEdit textEdit, XMLFormattingConstraints thisConstraints, IStructuredDocumentRegion currentDocumentRegion, IStructuredDocumentRegion previousDocumentRegion, String whitespaceStrategy, String indentStrategy, boolean addIndent, boolean handlePreviousWhitespace) {
    int availableLineWidth = -1;
    // if there is no previous document region, there is no need to indent
    // because we're at beginning of document
    if (previousDocumentRegion == null)
      return availableLineWidth;

    // only indent if ignoring whitespace or if collapsing and
    // there was a whitespace character before this region
    boolean canIndent = false;
    String previousRegionFullText = null;
    String previousRegionType = null;

    if ((XMLFormattingConstraints.IGNORE.equals(whitespaceStrategy)) || XMLFormattingConstraints.IGNOREANDTRIM.equals(whitespaceStrategy)) {
      // if ignoring, need to check if previous region was cdata
      previousRegionType = previousDocumentRegion.getType();
      if (DOMRegionContext.XML_CDATA_TEXT.equals(previousRegionType))
        canIndent = false;
      else
        canIndent = true;
    }
    else if (XMLFormattingConstraints.COLLAPSE.equals(whitespaceStrategy)) {
      // if collapsing, need to check if previous region ended in a
      // whitespace
      previousRegionType = previousDocumentRegion.getType();
      if (DOMRegionContext.XML_CONTENT.equals(previousRegionType)) {
        previousRegionFullText = previousDocumentRegion.getFullText();
        int length = previousRegionFullText.length();
        if (length > 1)
          canIndent = Character.isWhitespace(previousRegionFullText.charAt(length - 1));
      }
    }
    if (canIndent) {
      int indentStartOffset = currentDocumentRegion.getStartOffset();
      String whitespaceRun = null;

      // get previous region type if it was not previously retrieved
      if (previousRegionType == null)
        previousRegionType = previousDocumentRegion.getType();

      // get previous region's text if it was not previously retrieved
      if (previousRegionFullText == null && DOMRegionContext.XML_CONTENT.equals(previousRegionType))
        previousRegionFullText = previousDocumentRegion.getFullText();

      // if previous region was only whitespace, this may
      // already be indented, so need to make sure
      if ((previousRegionFullText != null) && (previousRegionFullText.trim().length() == 0)) {
        indentStartOffset = previousDocumentRegion.getStartOffset();
        whitespaceRun = previousRegionFullText;
      }
      if ((previousRegionFullText != null) && (whitespaceRun == null) && handlePreviousWhitespace) {
        whitespaceRun = getTrailingWhitespace(previousRegionFullText);
        indentStartOffset = previousDocumentRegion.getEndOffset() - whitespaceRun.length();
      }

      int indentLevel = thisConstraints.getIndentLevel();
      if (addIndent && XMLFormattingConstraints.INDENT.equals(indentStrategy)) {
        ++indentLevel;
        thisConstraints.setIndentLevel(indentLevel);
      }

      // indent if not already indented
      availableLineWidth = indentIfNotAlreadyIndented(textEdit, currentDocumentRegion, indentLevel, indentStartOffset, whitespaceRun);
    }
    return availableLineWidth;
  }

  /**
   * Allow exactly one whitespace in currentTextRegion. If there are more,
   * collapse to one. If there are none, insert one.
   *
   * @param textEdit
   * @param currentDocumentRegion
   * @param availableLineWidth
   * @param currentTextRegion
   */
  private void insertSpaceAndCollapse(TextEdit textEdit, IStructuredDocumentRegion currentDocumentRegion, int availableLineWidth, ITextRegion currentTextRegion) {
    int textLength = currentTextRegion.getTextLength();
    int regionLength = currentTextRegion.getLength();
    boolean thereAreSpaces = textLength < regionLength;
    int spacesStartOffset = currentDocumentRegion.getStartOffset(currentTextRegion) + textLength;

    if (thereAreSpaces) {
      String fullTagName = currentDocumentRegion.getFullText(currentTextRegion);
      String whitespaceRun = fullTagName.substring(textLength, regionLength);
      collapseSpaces(textEdit, spacesStartOffset, availableLineWidth, whitespaceRun);
    }
    else {
      // insert a space
      InsertEdit insertEdit = new InsertEdit(spacesStartOffset, SPACE);
      textEdit.addChild(insertEdit);
    }
  }

  private boolean shouldIndentBeforeAttribute(XMLFormattingConstraints constraints, ITextRegionList textRegions, int availableLineWidth, int currentTextRegionIndex, ITextRegion currentTextRegion, ITextRegion nextTextRegion) {
    boolean indentAttribute = false;

    // look ahead to see if going to hit max line width
    // something attrName
    int currentWidth = currentTextRegion.getTextLength() + nextTextRegion.getTextLength() + 1;
    if (currentWidth > availableLineWidth)
      indentAttribute = true;
    else {
      if ((currentTextRegionIndex + 2) < textRegions.size()) {
        // still okay, so try next region
        // something attrName=
        ITextRegion textRegion = textRegions.get(currentTextRegionIndex + 2);
        if (DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS.equals(textRegion.getType())) {
          ++currentWidth;
          if (currentWidth > availableLineWidth)
            indentAttribute = true;
          else {
            if ((currentTextRegionIndex + 3) < textRegions.size()) {
              // still okay, so try next region
              // something attrName=attrValue
              textRegion = textRegions.get(currentTextRegionIndex + 3);
              if (DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(textRegion.getType())) {
                currentWidth = +textRegion.getTextLength();
                if (currentWidth > availableLineWidth)
                  indentAttribute = true;
              }
            }
          }
        }
      }
    }
    return indentAttribute;
  }

  /**
   * Given the provided information (parentConstraints & currentDOMRegion),
   * update the formatting constraints (for this & child)
   *
   * @param parentConstraints
   *            can be null
   * @param thisConstraints
   *            can be null
   * @param childConstraints
   *            can be null
   * @param currentDOMRegion
   *            cannot be null
   */
  protected void updateFormattingConstraints(XMLFormattingConstraints parentConstraints, XMLFormattingConstraints thisConstraints, XMLFormattingConstraints childConstraints, DOMRegion currentDOMRegion) {
    IStructuredDocumentRegion currentRegion = currentDOMRegion.documentRegion;
    IDOMNode currentNode = currentDOMRegion.domNode;

    // default to whatever parent's constraint said to do
    if (parentConstraints != null) {
      if (thisConstraints != null) {
        thisConstraints.copyConstraints(parentConstraints);
      }
      if (childConstraints != null) {
        childConstraints.copyConstraints(parentConstraints);
        // if whitespace strategy was only a hint, null it out so
        // defaults are taken instead
        if (parentConstraints.isWhitespaceStrategyAHint())
          childConstraints.setWhitespaceStrategy(null);
      }
    }

    // set up constraints for direct children of document root
    Node parentNode = currentNode.getParentNode();
    if (parentNode != null && parentNode.getNodeType() == Node.DOCUMENT_NODE) {
      if (thisConstraints != null) {
        thisConstraints.setWhitespaceStrategy(XMLFormattingConstraints.IGNORE);
        thisConstraints.setIndentStrategy(XMLFormattingConstraints.NEW_LINE);
        thisConstraints.setIndentLevel(0);
      }
      if (childConstraints != null) {
        childConstraints.setWhitespaceStrategy(null);
        childConstraints.setIndentStrategy(null);
        childConstraints.setIndentLevel(0);
      }
    }

    // other conditions to check when setting up child constraints
    if (childConstraints != null) {
      XMLFormattingPreferences preferences = getFormattingPreferences();

      // if we're at document root, child tags should always just start
      // on a new line and have an indent level of 0
      if (currentNode.getNodeType() == Node.DOCUMENT_NODE) {
        childConstraints.setWhitespaceStrategy(XMLFormattingConstraints.IGNORE);
        childConstraints.setIndentStrategy(XMLFormattingConstraints.NEW_LINE);
        childConstraints.setIndentLevel(0);
      }
      else {
        // BUG108074 & BUG84688 - preserve whitespace in xsl:text &
        // xsl:attribute
        String nodeNamespaceURI = currentNode.getNamespaceURI();
        if (XSL_NAMESPACE.equals(nodeNamespaceURI)) {
          String nodeName = ((Element) currentNode).getLocalName();
          if (XSL_ATTRIBUTE.equals(nodeName) || XSL_TEXT.equals(nodeName)) {
            childConstraints.setWhitespaceStrategy(XMLFormattingConstraints.PRESERVE);
          }
        }
        else {
          // search within current tag for xml:space attribute
          ITextRegionList textRegions = currentRegion.getRegions();
          int i = 0;
          boolean xmlSpaceFound = false;
          boolean preserveFound = false;
          while (i < textRegions.size() && !xmlSpaceFound) {
            ITextRegion textRegion = textRegions.get(i);
            if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(textRegion.getType())) {
              String regionText = currentRegion.getText(textRegion);
              if (XML_SPACE.equals(regionText)) {
                if ((i + 1) < textRegions.size()) {
                  ++i;
                  textRegion = textRegions.get(i);
                  if (DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS.equals(textRegion.getType()) && ((i + 1) < textRegions.size())) {
                    ++i;
                    textRegion = textRegions.get(i);
                    regionText = currentRegion.getText(textRegion);
                    if (PRESERVE.equals(regionText) || PRESERVE_QUOTED.equals(regionText)) {
                      preserveFound = true;
                    }
                  }
                }
                xmlSpaceFound = true;
              }
            }
            ++i;
          }
          if (xmlSpaceFound) {
            if (preserveFound) {
              // preserve was found so set the strategy
              childConstraints.setWhitespaceStrategy(XMLFormattingConstraints.PRESERVE);
            }
            else {
              // xml:space was found but it was not collapse, so
              // use default whitespace strategy
              childConstraints.setWhitespaceStrategy(XMLFormattingConstraints.DEFAULT);
            }
          }
          else {
            // how to hande nodes that have nonwhitespace text
            // content
            NodeList nodeList = currentNode.getChildNodes();
            int length = nodeList.getLength();
            int index = 0;
            boolean textNodeFound = false;
            // BUG214516 - If the parent constraint is to preserve whitespace, child constraints should
            // still reflect the parent constraints
            while (index < length && !textNodeFound && parentConstraints != null && !XMLFormattingConstraints.PRESERVE.equals(parentConstraints.getWhitespaceStrategy())) {
              Node childNode = nodeList.item(index);
              if (childNode.getNodeType() == Node.TEXT_NODE) {
                textNodeFound = !((IDOMText) childNode).isElementContentWhitespace();
              }
              ++index;
            }
            if (textNodeFound) {
              if (length > 1) {
                // more in here than just text, so consider
                // this mixed content
                childConstraints.setWhitespaceStrategy(preferences.getMixedWhitespaceStrategy());
                childConstraints.setIndentStrategy(preferences.getMixedIndentStrategy());
              }
              else {
                // there's only text
                childConstraints.setWhitespaceStrategy(preferences.getTextWhitespaceStrategy());
                childConstraints.setIndentStrategy(preferences.getTextIndentStrategy());
              }
              childConstraints.setIsWhitespaceStrategyAHint(true);
              childConstraints.setIsIndentStrategyAHint(true);
            }

            // try referring to content model for information on
            // whitespace & indent strategy
            ModelQueryAdapter adapter = (ModelQueryAdapter) ((IDOMDocument) currentNode.getOwnerDocument()).getAdapterFor(ModelQueryAdapter.class);
            CMElementDeclaration elementDeclaration = (CMElementDeclaration) adapter.getModelQuery().getCMNode(currentNode);
            if (elementDeclaration != null) {
              // follow whitespace strategy preference for
              // pcdata content
              int contentType = elementDeclaration.getContentType();
             
              String facetValue = null;
              if(elementDeclaration.getDataType() != null)
                facetValue = (String) elementDeclaration.getDataType().getProperty(PROPERTY_WHITESPACE_FACET);
              if(facetValue != null) {
                if(PRESERVE.equals(facetValue))
                  childConstraints.setWhitespaceStrategy(XMLFormattingConstraints.PRESERVE);
                // For XSD types, "collapse" corresponds to the IGNOREANDTRIM strategy
                else if(COLLAPSE.equals(facetValue))
                  childConstraints.setWhitespaceStrategy(XMLFormattingConstraints.IGNOREANDTRIM);
                else if(REPLACE.equals(facetValue))
                  childConstraints.setWhitespaceStrategy(XMLFormattingConstraints.REPLACE);
              }
              else if (contentType == CMElementDeclaration.PCDATA && parentConstraints != null && !XMLFormattingConstraints.PRESERVE.equals(parentConstraints.getWhitespaceStrategy())) {
                childConstraints.setWhitespaceStrategy(preferences.getPCDataWhitespaceStrategy());
              }
              else if (contentType == CMElementDeclaration.ELEMENT && parentConstraints != null && !XMLFormattingConstraints.PRESERVE.equals(parentConstraints.getWhitespaceStrategy())) {
                childConstraints.setWhitespaceStrategy(XMLFormattingConstraints.IGNORE);
                childConstraints.setIndentStrategy(XMLFormattingConstraints.INDENT);
                childConstraints.setIsWhitespaceStrategyAHint(true);
                childConstraints.setIsIndentStrategyAHint(true);
              }
              else {
                // look for xml:space in content model
                CMNamedNodeMap cmAttributes = elementDeclaration.getAttributes();

                // Not needed - we're looking for xml:space
                //CMNamedNodeMapImpl allAttributes = new CMNamedNodeMapImpl(cmAttributes);
                //List nodes = ModelQueryUtil.getModelQuery(currentNode.getOwnerDocument()).getAvailableContent((Element) currentNode, elementDeclaration, ModelQuery.INCLUDE_ATTRIBUTES);
                //for (int k = 0; k < nodes.size(); k++) {
                //  CMNode cmnode = (CMNode) nodes.get(k);
                //  if (cmnode.getNodeType() == CMNode.ATTRIBUTE_DECLARATION) {
                //    allAttributes.put(cmnode);
                //  }
                //}
                //cmAttributes = allAttributes;

                // Check implied values from the DTD way.
                CMAttributeDeclaration attributeDeclaration = (CMAttributeDeclaration) cmAttributes.getNamedItem(XML_SPACE);
                if (attributeDeclaration != null) {
                  // CMAttributeDeclaration found, check
                  // it
                  // out.

                  //BUG214516/196544 - Fixed NPE that was caused by an attr having
                  // a null attr type
                  String defaultValue = null;
                  CMDataType attrType = attributeDeclaration.getAttrType();
                  if (attrType != null) {
                    if ((attrType.getImpliedValueKind() != CMDataType.IMPLIED_VALUE_NONE) && attrType.getImpliedValue() != null)
                      defaultValue = attrType.getImpliedValue();
                    else if ((attrType.getEnumeratedValues() != null) && (attrType.getEnumeratedValues().length > 0)) {
                      defaultValue = attrType.getEnumeratedValues()[0];
                    }
                  }

                  // xml:space="preserve" means preserve
                  // space,
                  // everything else means back to
                  // default.
                  if (PRESERVE.equals(defaultValue))
                    childConstraints.setWhitespaceStrategy(XMLFormattingConstraints.PRESERVE);
                  else
                    childConstraints.setWhitespaceStrategy(XMLFormattingConstraints.DEFAULT);
                }
                // If the node has no attributes, inherit the parents whitespace strategy
                else {
                  if (parentConstraints != null)
                    childConstraints.setWhitespaceStrategy(parentConstraints.getWhitespaceStrategy());
                  else
                    childConstraints.setWhitespaceStrategy(null);
                }
              }
            }
          }
        }
      }
      // set default values according to preferences
      if (childConstraints.getWhitespaceStrategy() == null) {
        childConstraints.setWhitespaceStrategy(preferences.getElementWhitespaceStrategy());
      }
      if (childConstraints.getIndentStrategy() == null) {
        childConstraints.setIndentStrategy(preferences.getElementIndentStrategy());
      }
    }
  }

  /**
   * Calculates the current available line width given fullText.
   *
   * @param fullText
   * @param availableLineWidth
   * @param maxAvailableLineWidth
   * @return
   */
  private int updateLineWidthWithLastLine(String fullText, int availableLineWidth) {
    int maxAvailableLineWidth = getFormattingPreferences().getMaxLineWidth();
    int lineWidth = availableLineWidth;
    if (fullText != null) {
      int textLength = fullText.length();
      // update available line width
      // find last newline
      int lastLFOffset = fullText.lastIndexOf('\n');
      int lastCROffset = fullText.lastIndexOf('\r');
      // all text was on 1 line
      if (lastLFOffset == -1 && lastCROffset == -1) {
        // just subtract text length from current
        // available line width
        lineWidth -= fullText.length();
      }
      else {
        // calculate available line width of last line
        int lastNewLine = Math.max(lastLFOffset, lastCROffset);
        lineWidth = maxAvailableLineWidth - (textLength - lastNewLine);
      }
    }
    return lineWidth;
  }

  private String getLineDelimiter(IStructuredDocumentRegion currentRegion) {
    IStructuredDocument doc = currentRegion.getParentDocument();
    int line = doc.getLineOfOffset(currentRegion.getStartOffset());
    String lineDelimiter = doc.getLineDelimiter();
    try {
      if (line > 0) {
        lineDelimiter = doc.getLineDelimiter(line - 1);
      }
    }
    catch (BadLocationException e) {
      // log for now, unless we find reason not to
      Logger.log(Logger.INFO, e.getMessage());
    }
    // BUG115716: if cannot get line delimiter from current line, just
    // use default line delimiter
    if (lineDelimiter == null)
      lineDelimiter = doc.getLineDelimiter();
    return lineDelimiter;
  }
 
  private String extractLineDelimiters(String base, IStructuredDocumentRegion currentRegion) {
    String lineDelimiter = getLineDelimiter(currentRegion);
    StringBuffer sb = new StringBuffer();
    for(int index = 0; index < base.length();) {
      index = base.indexOf(lineDelimiter, index);
      if(index++ >= 0)
        sb.append(lineDelimiter);
      else
        break;
    }
    return sb.toString();
  }

  void setProgressMonitor(IProgressMonitor monitor) {
    fProgressMonitor = monitor;
  }
}
TOP

Related Classes of org.eclipse.wst.xml.core.internal.formatter.DefaultXMLPartitionFormatter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.