Package org.eclipse.wst.html.core.internal.cleanup

Source Code of org.eclipse.wst.html.core.internal.cleanup.ElementNodeCleanupHandler

/*******************************************************************************
* Copyright (c) 2004, 2010 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 (Intalion) - Cleanup Repeated Conditional check in isXMLType method
*******************************************************************************/
package org.eclipse.wst.html.core.internal.cleanup;



import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.wst.css.core.internal.formatter.CSSSourceFormatter;
import org.eclipse.wst.css.core.internal.formatter.CSSSourceFormatterFactory;
import org.eclipse.wst.css.core.internal.provisional.adapters.IStyleDeclarationAdapter;
import org.eclipse.wst.css.core.internal.provisional.document.ICSSModel;
import org.eclipse.wst.css.core.internal.provisional.document.ICSSNode;
import org.eclipse.wst.html.core.internal.Logger;
import org.eclipse.wst.html.core.internal.preferences.HTMLCorePreferenceNames;
import org.eclipse.wst.sse.core.internal.cleanup.IStructuredCleanupHandler;
import org.eclipse.wst.sse.core.internal.provisional.INodeAdapter;
import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier;
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.ITextRegionContainer;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.sse.core.utils.StringUtils;
import org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration;
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.contentmodel.modelquery.ModelQuery;
import org.eclipse.wst.xml.core.internal.modelquery.ModelQueryUtil;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.eclipse.wst.xml.core.internal.provisional.document.ISourceGenerator;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

// nakamori_TODO: check and remove CSS formatting

public class ElementNodeCleanupHandler extends AbstractNodeCleanupHandler {

  /** Non-NLS strings */
  protected static final String START_TAG_OPEN = "<"; //$NON-NLS-1$
  protected static final String END_TAG_OPEN = "</"; //$NON-NLS-1$
  protected static final String TAG_CLOSE = ">"; //$NON-NLS-1$
  protected static final String EMPTY_TAG_CLOSE = "/>"; //$NON-NLS-1$
  protected static final String SINGLE_QUOTES = "''"; //$NON-NLS-1$
  protected static final String DOUBLE_QUOTES = "\"\""; //$NON-NLS-1$
  protected static final char SINGLE_QUOTE = '\''; //$NON-NLS-1$
  protected static final char DOUBLE_QUOTE = '\"'; //$NON-NLS-1$

  public Node cleanup(Node node) {
    IDOMNode renamedNode = (IDOMNode) cleanupChildren(node);

    // call quoteAttrValue() first so it will close any unclosed attr
    // quoteAttrValue() will return the new start tag if there is a
    // structure change
    renamedNode = quoteAttrValue(renamedNode);

    // insert tag close if missing
    // if node is not comment tag
    // and not implicit tag
    if (!((IDOMElement) renamedNode).isCommentTag() && (renamedNode.getStartStructuredDocumentRegion() != null)) {
      IDOMModel structuredModel = renamedNode.getModel();

      // save start offset before insertTagClose()
      // or else renamedNode.getStartOffset() will be zero if
      // renamedNode replaced by insertTagClose()
      int startTagStartOffset = renamedNode.getStartOffset();

      // for start tag
      IStructuredDocumentRegion startTagStructuredDocumentRegion = renamedNode.getStartStructuredDocumentRegion();
      insertTagClose(structuredModel, startTagStructuredDocumentRegion);

      // update renamedNode and startTagStructuredDocumentRegion after
      // insertTagClose()
      renamedNode = (IDOMNode) structuredModel.getIndexedRegion(startTagStartOffset);
      startTagStructuredDocumentRegion = renamedNode.getStartStructuredDocumentRegion();

      // for end tag
      IStructuredDocumentRegion endTagStructuredDocumentRegion = renamedNode.getEndStructuredDocumentRegion();
      if (endTagStructuredDocumentRegion != startTagStructuredDocumentRegion)
        insertTagClose(structuredModel, endTagStructuredDocumentRegion);
    }

    // call insertMissingTags() next, it will generate implicit tags if
    // there are any
    // insertMissingTags() will return the new missing start tag if one is
    // missing
    // then compress any empty element tags
    // applyTagNameCase() will return the renamed node.
    // The renamed/new node will be saved and returned to caller when all
    // cleanup is done.
    renamedNode = insertMissingTags(renamedNode);
    renamedNode = compressEmptyElementTag(renamedNode);
    renamedNode = insertRequiredAttrs(renamedNode);
    renamedNode = applyTagNameCase(renamedNode);
    applyAttrNameCase(renamedNode);
    cleanupCSSAttrValue(renamedNode);

    return renamedNode;
  }

  /**
   * Checks if cleanup should modify case. Returns true case should be
   * preserved, false otherwise.
   *
   * @param element
   * @return true if element is case sensitive, false otherwise
   */
  private boolean shouldPreserveCase(IDOMElement element) {
    // case option can be applied to no namespace tags
    return !element.isGlobalTag();
    /*
     * ModelQueryAdapter mqadapter = (ModelQueryAdapter)
     * element.getAdapterFor(ModelQueryAdapter.class); ModelQuery mq =
     * null; CMNode nodedecl = null; if (mqadapter != null) mq =
     * mqadapter.getModelQuery(); if (mq != null) nodedecl =
     * mq.getCMNode(node); // if a Node isn't recognized as HTML or is and
     * cares about case, do not alter it // if (nodedecl == null ||
     * (nodedecl instanceof HTMLCMNode && ((HTMLCMNode)
     * nodedecl).shouldIgnoreCase())) if (!
     * nodedecl.supports(HTMLCMProperties.SHOULD_IGNORE_CASE)) return
     * false; return
     * ((Boolean)cmnode.getProperty(HTMLCMProperties.SHOULD_IGNORE_CASE)).booleanValue();
     */
  }

  /**
   * Checks if cleanup should force modifying element name to all lowercase.
   *
   * @param element
   * @return true if cleanup should lowercase element name, false otherwise
   */
  private boolean isXMLTag(IDOMElement element) {
    return element.isXMLTag();
  }

  protected void applyAttrNameCase(IDOMNode node) {
    IDOMElement element = (IDOMElement) node;
    if (element.isCommentTag())
      return; // do nothing

    int attrNameCase = HTMLCorePreferenceNames.ASIS;
    if (!shouldPreserveCase(element)) {
      if (isXMLTag(element))
        attrNameCase = HTMLCorePreferenceNames.LOWER;
      else
        attrNameCase = getCleanupPreferences().getAttrNameCase();
    }

    NamedNodeMap attributes = node.getAttributes();
    int attributesLength = attributes.getLength();

    for (int i = 0; i < attributesLength; i++) {
      IDOMNode eachAttr = (IDOMNode) attributes.item(i);
      if (hasNestedRegion(eachAttr.getNameRegion()))
        continue;
      String oldAttrName = eachAttr.getNodeName();
      String newAttrName = oldAttrName;
      /*
       * 254961 - all HTML tag names and attribute names should be in
       * English even for HTML files in other languages like Japanese or
       * Turkish. English locale should be used to convert between
       * uppercase and lowercase (otherwise "link" would be converted to
       * Turkish "I Overdot Capital").
       */
      if (attrNameCase == HTMLCorePreferenceNames.LOWER)
        newAttrName = oldAttrName.toLowerCase(Locale.US);
      else if (attrNameCase == HTMLCorePreferenceNames.UPPER)
        newAttrName = oldAttrName.toUpperCase(Locale.US);

      if (newAttrName.compareTo(oldAttrName) != 0) {
        int attrNameStartOffset = eachAttr.getStartOffset();
        int attrNameLength = oldAttrName.length();

        IDOMModel structuredModel = node.getModel();
        IStructuredDocument structuredDocument = structuredModel.getStructuredDocument();
        replaceSource(structuredModel, structuredDocument, attrNameStartOffset, attrNameLength, newAttrName);
      }
    }
  }

  /**
   * True if container has nested regions, meaning container is probably too
   * complicated (like JSP regions) to validate with this validator.
   */
  private boolean hasNestedRegion(ITextRegion container) {
    if (!(container instanceof ITextRegionContainer))
      return false;
    ITextRegionList regions = ((ITextRegionContainer) container).getRegions();
    if (regions == null)
      return false;
    return true;
  }

  protected IDOMNode applyTagNameCase(IDOMNode node) {
    IDOMElement element = (IDOMElement) node;
    if (element.isCommentTag())
      return node; // do nothing

    int tagNameCase = HTMLCorePreferenceNames.ASIS;

    if (!shouldPreserveCase(element)) {
      if (isXMLTag(element))
        tagNameCase = HTMLCorePreferenceNames.LOWER;
      else
        tagNameCase = getCleanupPreferences().getTagNameCase();
    }

    String oldTagName = node.getNodeName();
    String newTagName = oldTagName;
    IDOMNode newNode = node;

    /*
     * 254961 - all HTML tag names and attribute names should be in
     * English even for HTML files in other languages like Japanese or
     * Turkish. English locale should be used to convert between uppercase
     * and lowercase (otherwise "link" would be converted to Turkish "I
     * Overdot Capital").
     */
    if (tagNameCase == HTMLCorePreferenceNames.LOWER)
      newTagName = oldTagName.toLowerCase(Locale.US);
    else if (tagNameCase == HTMLCorePreferenceNames.UPPER)
      newTagName = oldTagName.toUpperCase(Locale.US);

    IDOMModel structuredModel = node.getModel();
    IStructuredDocument structuredDocument = structuredModel.getStructuredDocument();

    IStructuredDocumentRegion startTagStructuredDocumentRegion = node.getStartStructuredDocumentRegion();
    if (startTagStructuredDocumentRegion != null) {
      ITextRegionList regions = startTagStructuredDocumentRegion.getRegions();
      if (regions != null && regions.size() > 0) {
        ITextRegion startTagNameRegion = regions.get(1);
        int startTagNameStartOffset = startTagStructuredDocumentRegion.getStartOffset(startTagNameRegion);
        int startTagNameLength = startTagStructuredDocumentRegion.getTextEndOffset(startTagNameRegion) - startTagNameStartOffset;

        replaceSource(structuredModel, structuredDocument, startTagNameStartOffset, startTagNameLength, newTagName);
        newNode = (IDOMNode) structuredModel.getIndexedRegion(startTagNameStartOffset); // save
        // new
        // node
      }
    }

    IStructuredDocumentRegion endTagStructuredDocumentRegion = node.getEndStructuredDocumentRegion();
    if (endTagStructuredDocumentRegion != null) {
      ITextRegionList regions = endTagStructuredDocumentRegion.getRegions();
      if (regions != null && regions.size() > 0) {
        ITextRegion endTagNameRegion = regions.get(1);
        int endTagNameStartOffset = endTagStructuredDocumentRegion.getStartOffset(endTagNameRegion);
        int endTagNameLength = endTagStructuredDocumentRegion.getTextEndOffset(endTagNameRegion) - endTagNameStartOffset;

        if (startTagStructuredDocumentRegion != endTagStructuredDocumentRegion)
          replaceSource(structuredModel, structuredDocument, endTagNameStartOffset, endTagNameLength, newTagName);
      }
    }

    return newNode;
  }

  protected Node cleanupChildren(Node node) {
    Node parentNode = node;

    if (node != null) {
      Node childNode = node.getFirstChild();
      HTMLCleanupHandlerFactory factory = HTMLCleanupHandlerFactory.getInstance();
      while (childNode != null) {
        // cleanup this child node
        IStructuredCleanupHandler cleanupHandler = factory.createHandler(childNode, getCleanupPreferences());
        childNode = cleanupHandler.cleanup(childNode);

        // get new parent node
        parentNode = childNode.getParentNode();

        // get next child node
        childNode = childNode.getNextSibling();
      }
    }

    return parentNode;
  }

  /**
   */
  protected void cleanupCSSAttrValue(IDOMNode node) {
    if (node == null || node.getNodeType() != Node.ELEMENT_NODE)
      return;
    IDOMElement element = (IDOMElement) node;
    if (!element.isGlobalTag())
      return;

    Attr attr = element.getAttributeNode("style"); //$NON-NLS-1$
    if (attr == null)
      return;
    String value = getCSSValue(attr);
    if (value == null)
      return;
    String oldValue = ((IDOMNode) attr).getValueSource();
    if (oldValue != null && value.equals(oldValue))
      return;
    attr.setValue(value);
  }

  /**
   */
  private ICSSModel getCSSModel(Attr attr) {
    if (attr == null)
      return null;
    INodeNotifier notifier = (INodeNotifier) attr.getOwnerElement();
    if (notifier == null)
      return null;
    INodeAdapter adapter = notifier.getAdapterFor(IStyleDeclarationAdapter.class);
    if (adapter == null)
      return null;
    if (!(adapter instanceof IStyleDeclarationAdapter))
      return null;
    IStyleDeclarationAdapter styleAdapter = (IStyleDeclarationAdapter) adapter;
    return styleAdapter.getModel();
  }

  /**
   */
  private String getCSSValue(Attr attr) {
    ICSSModel model = getCSSModel(attr);
    if (model == null)
      return null;
    ICSSNode document = model.getDocument();
    if (document == null)
      return null;
    INodeNotifier notifier = (INodeNotifier) document;
    CSSSourceFormatter formatter = (CSSSourceFormatter) notifier.getAdapterFor(CSSSourceFormatter.class);
    // try another way to get formatter
    if (formatter == null)
      formatter = CSSSourceFormatterFactory.getInstance().getSourceFormatter(notifier);
    if (formatter == null)
      return null;
    StringBuffer buffer = formatter.cleanup(document);
    if (buffer == null)
      return null;
    return buffer.toString();
  }

  private boolean isEmptyElement(IDOMElement element) {
    Document document = element.getOwnerDocument();
    if (document == null)
      // undefined tag, return default
      return false;

    ModelQuery modelQuery = ModelQueryUtil.getModelQuery(document);
    if (modelQuery == null)
      // undefined tag, return default
      return false;

    CMElementDeclaration decl = modelQuery.getCMElementDeclaration(element);
    if (decl == null)
      // undefined tag, return default
      return false;

    return (decl.getContentType() == CMElementDeclaration.EMPTY);
  }

  protected IDOMNode insertEndTag(IDOMNode node) {
    IDOMElement element = (IDOMElement) node;

    int startTagStartOffset = node.getStartOffset();
    IDOMModel structuredModel = node.getModel();
    IDOMNode newNode = null;

    if (element.isCommentTag()) {
      // do nothing
    }
    else if (isEmptyElement(element)) {
      IStructuredDocument structuredDocument = structuredModel.getStructuredDocument();
      IStructuredDocumentRegion startStructuredDocumentRegion = node.getStartStructuredDocumentRegion();
      ITextRegionList regions = startStructuredDocumentRegion.getRegions();
      ITextRegion lastRegion = regions.get(regions.size() - 1);
      replaceSource(structuredModel, structuredDocument, startStructuredDocumentRegion.getStartOffset(lastRegion), lastRegion.getLength(), EMPTY_TAG_CLOSE);

      if (regions.size() > 1) {
        ITextRegion regionBeforeTagClose = regions.get(regions.size() - 1 - 1);

        // insert a space separator before tag close if the previous
        // region does not have extra spaces
        if (regionBeforeTagClose.getTextLength() == regionBeforeTagClose.getLength())
          replaceSource(structuredModel, structuredDocument, startStructuredDocumentRegion.getStartOffset(lastRegion), 0, " "); //$NON-NLS-1$
      }
    }
    else {
      String tagName = node.getNodeName();
      String endTag = END_TAG_OPEN.concat(tagName).concat(TAG_CLOSE);

      IDOMNode lastChild = (IDOMNode) node.getLastChild();
      int endTagStartOffset = 0;
      if (lastChild != null)
        // if this node has children, insert the end tag after the
        // last child
        endTagStartOffset = lastChild.getEndOffset();
      else
        // if this node does not has children, insert the end tag
        // after the start tag
        endTagStartOffset = node.getEndOffset();

      IStructuredDocument structuredDocument = structuredModel.getStructuredDocument();
      replaceSource(structuredModel, structuredDocument, endTagStartOffset, 0, endTag);
    }

    newNode = (IDOMNode) structuredModel.getIndexedRegion(startTagStartOffset); // save
    // new
    // node

    return newNode;
  }

  protected IDOMNode insertMissingTags(IDOMNode node) {
    boolean insertMissingTags = getCleanupPreferences().getInsertMissingTags();
    IDOMNode newNode = node;

    if (insertMissingTags) {
      IStructuredDocumentRegion startTagStructuredDocumentRegion = node.getStartStructuredDocumentRegion();
      if (startTagStructuredDocumentRegion == null) {
        // implicit start tag; generate tag for it
        newNode = insertStartTag(node);
        startTagStructuredDocumentRegion = newNode.getStartStructuredDocumentRegion();
      }

      IStructuredDocumentRegion endTagStructuredDocumentRegion = newNode.getEndStructuredDocumentRegion();

      ITextRegionList regionList = startTagStructuredDocumentRegion.getRegions();
      if (startTagStructuredDocumentRegion != null && regionList != null && regionList.get(regionList.size() - 1).getType() == DOMRegionContext.XML_EMPTY_TAG_CLOSE) {

      }
      else {
        if (startTagStructuredDocumentRegion == null) {
          // start tag missing
          if (isStartTagRequired(newNode))
            newNode = insertStartTag(newNode);
        }
        else if (endTagStructuredDocumentRegion == null) {
          // end tag missing
          if (isEndTagRequired(newNode))
            newNode = insertEndTag(newNode);
        }
      }
    }

    return newNode;
  }

  protected IDOMNode insertStartTag(IDOMNode node) {
    IDOMElement element = (IDOMElement) node;
    if (element.isCommentTag())
      return node; // do nothing

    IDOMNode newNode = null;

    String tagName = node.getNodeName();
    String startTag = START_TAG_OPEN.concat(tagName).concat(TAG_CLOSE);
    int startTagStartOffset = node.getStartOffset();

    IDOMModel structuredModel = node.getModel();
    IStructuredDocument structuredDocument = structuredModel.getStructuredDocument();
    replaceSource(structuredModel, structuredDocument, startTagStartOffset, 0, startTag);
    newNode = (IDOMNode) structuredModel.getIndexedRegion(startTagStartOffset); // save
    // new
    // node

    return newNode;
  }

  protected void insertTagClose(IDOMModel structuredModel, IStructuredDocumentRegion flatNode) {
    if ((flatNode != null) && (flatNode.getRegions() != null)) {
      ITextRegionList regionList = flatNode.getRegions();
      ITextRegion lastRegion = regionList.get(regionList.size() - 1);
      if (lastRegion != null) {
        String regionType = lastRegion.getType();
        if ((regionType != DOMRegionContext.XML_EMPTY_TAG_CLOSE) && (regionType != DOMRegionContext.XML_TAG_CLOSE)) {
          IStructuredDocument structuredDocument = structuredModel.getStructuredDocument();

          // insert ">" after lastRegion of flatNode
          // as in "<a</a>" if flatNode is for start tag, or in
          // "<a></a" if flatNode is for end tag
          replaceSource(structuredModel, structuredDocument, flatNode.getTextEndOffset(lastRegion), 0, ">"); //$NON-NLS-1$
        }
      }
    }
  }

  protected boolean isEndTagRequired(IDOMNode node) {
    if (node == null)
      return false;
    return node.isContainer();
  }

  /**
   * The end tags of HTML EMPTY content type, such as IMG, and HTML
   * undefined tags are parsed separately from the start tags. So inserting
   * the missing start tag is useless and even harmful.
   */
  protected boolean isStartTagRequired(IDOMNode node) {
    if (node == null)
      return false;
    return node.isContainer();
  }

  protected boolean isXMLType(IDOMModel structuredModel) {
    boolean result = false;

    if (structuredModel != null) {
      IDOMDocument document = structuredModel.getDocument();

      if (document != null)
        result = document.isXMLType();
    }

    return result;
  }

  protected IDOMNode quoteAttrValue(IDOMNode node) {
    IDOMElement element = (IDOMElement) node;
    if (element.isCommentTag())
      return node; // do nothing

    boolean quoteAttrValues = getCleanupPreferences().getQuoteAttrValues();
    IDOMNode newNode = node;

    if (quoteAttrValues) {
      NamedNodeMap attributes = newNode.getAttributes();
      int attributesLength = attributes.getLength();
      ISourceGenerator generator = node.getModel().getGenerator();

      for (int i = 0; i < attributesLength; i++) {
        attributes = newNode.getAttributes();
        attributesLength = attributes.getLength();
        IDOMAttr eachAttr = (IDOMAttr) attributes.item(i);
        // ITextRegion oldAttrValueRegion = eachAttr.getValueRegion();
        String oldAttrValue = eachAttr.getValueRegionText();
        if (oldAttrValue == null) {
          IDOMModel structuredModel = node.getModel();
          if (isXMLType(structuredModel)) {
            // TODO: Kit, please check. Is there any way to not
            // rely on getting regions from attributes?
            String newAttrValue = "=\"" + eachAttr.getNameRegionText() + "\""; //$NON-NLS-1$ //$NON-NLS-2$

            IStructuredDocument structuredDocument = structuredModel.getStructuredDocument();
            replaceSource(structuredModel, structuredDocument, eachAttr.getNameRegionEndOffset(), 0, newAttrValue);
            newNode = (IDOMNode) structuredModel.getIndexedRegion(node.getStartOffset()); // save
            // new
            // node
          }
        }
        else {

          char quote = StringUtils.isQuoted(oldAttrValue) ? oldAttrValue.charAt(0) : DOUBLE_QUOTE;
          String newAttrValue = generator.generateAttrValue(eachAttr, quote);

          // There is a problem in
          // StructuredDocumentRegionUtil.getAttrValue(ITextRegion)
          // when the region is instanceof ContextRegion.
          // Workaround for now...
          if (oldAttrValue.length() == 1) {
            char firstChar = oldAttrValue.charAt(0);
            if (firstChar == SINGLE_QUOTE)
              newAttrValue = SINGLE_QUOTES;
            else if (firstChar == DOUBLE_QUOTE)
              newAttrValue = DOUBLE_QUOTES;
          }

          if (newAttrValue != null) {
            if (newAttrValue.compareTo(oldAttrValue) != 0) {
              int attrValueStartOffset = eachAttr.getValueRegionStartOffset();
              int attrValueLength = oldAttrValue.length();
              int startTagStartOffset = node.getStartOffset();

              IDOMModel structuredModel = node.getModel();
              IStructuredDocument structuredDocument = structuredModel.getStructuredDocument();
              replaceSource(structuredModel, structuredDocument, attrValueStartOffset, attrValueLength, newAttrValue);
              newNode = (IDOMNode) structuredModel.getIndexedRegion(startTagStartOffset); // save
              // new
              // node
            }
          }
        }
      }
    }

    return newNode;
  }

  private IDOMNode insertRequiredAttrs(IDOMNode node) {
    boolean insertRequiredAttrs = getCleanupPreferences().getInsertRequiredAttrs();
    IDOMNode newNode = node;

    if (insertRequiredAttrs) {
      List requiredAttrs = getRequiredAttrs(newNode);
      if (requiredAttrs.size() > 0) {
        NamedNodeMap currentAttrs = node.getAttributes();
        List insertAttrs = new ArrayList();
        if (currentAttrs.getLength() == 0)
          insertAttrs.addAll(requiredAttrs);
        else {
          for (int i = 0; i < requiredAttrs.size(); i++) {
            String requiredAttrName = ((CMAttributeDeclaration) requiredAttrs.get(i)).getAttrName();
            boolean found = false;
            for (int j = 0; j < currentAttrs.getLength(); j++) {
              String currentAttrName = currentAttrs.item(j).getNodeName();
              if (requiredAttrName.compareToIgnoreCase(currentAttrName) == 0) {
                found = true;
                break;
              }
            }
            if (!found)
              insertAttrs.add(requiredAttrs.get(i));
          }
        }
        if (insertAttrs.size() > 0) {
          IStructuredDocumentRegion startStructuredDocumentRegion = newNode.getStartStructuredDocumentRegion();
          int index = startStructuredDocumentRegion.getEndOffset();
          ITextRegion lastRegion = startStructuredDocumentRegion.getLastRegion();
          if (lastRegion.getType() == DOMRegionContext.XML_TAG_CLOSE) {
            index--;
            lastRegion = startStructuredDocumentRegion.getRegionAtCharacterOffset(index - 1);
          }
          else if (lastRegion.getType() == DOMRegionContext.XML_EMPTY_TAG_CLOSE) {
            index = index - 2;
            lastRegion = startStructuredDocumentRegion.getRegionAtCharacterOffset(index - 1);
          }
          MultiTextEdit multiTextEdit = new MultiTextEdit();
          try {
            for (int i = insertAttrs.size() - 1; i >= 0; i--) {
              CMAttributeDeclaration attrDecl = (CMAttributeDeclaration) insertAttrs.get(i);
              String requiredAttributeName = attrDecl.getAttrName();
              String defaultValue = attrDecl.getDefaultValue();
              if (defaultValue == null)
                defaultValue = ""; //$NON-NLS-1$
              String nameAndDefaultValue = " "; //$NON-NLS-1$
              if (i == 0 && lastRegion.getLength() > lastRegion.getTextLength())
                nameAndDefaultValue = ""; //$NON-NLS-1$
              nameAndDefaultValue += requiredAttributeName + "=\"" + defaultValue + "\""; //$NON-NLS-1$ //$NON-NLS-2$
              multiTextEdit.addChild(new InsertEdit(index, nameAndDefaultValue));
              // BUG3381: MultiTextEdit applies all child
              // TextEdit's basing on offsets
              // in the document before the first TextEdit, not
              // after each
              // child TextEdit. Therefore, do not need to
              // advance the index.
              // index += nameAndDefaultValue.length();
            }
            multiTextEdit.apply(newNode.getStructuredDocument());
          }
          catch (BadLocationException e) {
            // log or now, unless we find reason not to
            Logger.log(Logger.INFO, e.getMessage());
          }
        }
      }
    }

    return newNode;
  }


  protected ModelQuery getModelQuery(Node node) {
    ModelQuery result = null;
    if (node.getNodeType() == Node.DOCUMENT_NODE) {
      result = ModelQueryUtil.getModelQuery((Document) node);
    }
    else {
      result = ModelQueryUtil.getModelQuery(node.getOwnerDocument());
    }
    return result;
  }

  protected List getRequiredAttrs(Node node) {
    List result = new ArrayList();

    ModelQuery modelQuery = getModelQuery(node);
    if (modelQuery != null) {
      CMElementDeclaration elementDecl = modelQuery.getCMElementDeclaration((Element) node);
      if (elementDecl != null) {
        CMNamedNodeMap attrMap = elementDecl.getAttributes();
        Iterator it = attrMap.iterator();
        CMAttributeDeclaration attr = null;
        while (it.hasNext()) {
          attr = (CMAttributeDeclaration) it.next();
          if (attr.getUsage() == CMAttributeDeclaration.REQUIRED) {
            result.add(attr);
          }
        }
      }
    }

    return result;
  }

  /**
   * <p>Compress empty element tags if the prefence is set to do so</p>
   *
   * @copyof org.eclipse.wst.xml.core.internal.cleanup.ElementNodeCleanupHandler#compressEmptyElementTag
   *
   * @param node the {@link IDOMNode} to possible compress
   * @return the compressed node if the given node should be compressed, else the node as it was given
   */
  private IDOMNode compressEmptyElementTag(IDOMNode node) {
    boolean compressEmptyElementTags = getCleanupPreferences().getCompressEmptyElementTags();
    IDOMNode newNode = node;

    IStructuredDocumentRegion startTagStructuredDocumentRegion = newNode.getFirstStructuredDocumentRegion();
    IStructuredDocumentRegion endTagStructuredDocumentRegion = newNode.getLastStructuredDocumentRegion();

    //only compress tags if they are empty
    if ((compressEmptyElementTags && startTagStructuredDocumentRegion != endTagStructuredDocumentRegion &&
        startTagStructuredDocumentRegion != null)) {

      //only compress end tags if its XHTML or not a container
      if(isXMLTag((IDOMElement)newNode) || !newNode.isContainer()) {
        ITextRegionList regions = startTagStructuredDocumentRegion.getRegions();
        ITextRegion lastRegion = regions.get(regions.size() - 1);
        // format children and end tag if not empty element tag
        if (lastRegion.getType() != DOMRegionContext.XML_EMPTY_TAG_CLOSE) {
          NodeList childNodes = newNode.getChildNodes();
          if (childNodes == null || childNodes.getLength() == 0 || (childNodes.getLength() == 1 && (childNodes.item(0)).getNodeType() == Node.TEXT_NODE && ((childNodes.item(0)).getNodeValue().trim().length() == 0))) {
            IDOMModel structuredModel = newNode.getModel();
            IStructuredDocument structuredDocument = structuredModel.getStructuredDocument();

            int startTagStartOffset = newNode.getStartOffset();
            int offset = endTagStructuredDocumentRegion.getStart();
            int length = endTagStructuredDocumentRegion.getLength();
            structuredDocument.replaceText(structuredDocument, offset, length, ""); //$NON-NLS-1$
            newNode = (IDOMNode) structuredModel.getIndexedRegion(startTagStartOffset); // save

            offset = startTagStructuredDocumentRegion.getStart() + lastRegion.getStart();
            structuredDocument.replaceText(structuredDocument, offset, 0, "/"); //$NON-NLS-1$
            newNode = (IDOMNode) structuredModel.getIndexedRegion(startTagStartOffset); // save
          }
        }
      }
    }

    return newNode;
  }
}
TOP

Related Classes of org.eclipse.wst.html.core.internal.cleanup.ElementNodeCleanupHandler

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.