gt: 				newNode = insertStartTag(node);
context: /*******************************************************************************
   * Copyright (c) 2001, 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
   *     Jens Lukowski/Innoopract - initial renaming/restructuring
   *     David Carver (Intalion) - Cleanup Repeated Conditional check in isXMLType method
   *******************************************************************************/
  package org.eclipse.wst.xml.core.internal.cleanup;
  
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
  
  import org.eclipse.jface.text.BadLocationException;
  import org.eclipse.text.edits.InsertEdit;
  import org.eclipse.text.edits.MultiTextEdit;
  import org.eclipse.wst.sse.core.internal.cleanup.IStructuredCleanupHandler;
  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.sse.core.utils.StringUtils;
  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.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.Document;
  import org.w3c.dom.Element;
  import org.w3c.dom.NamedNodeMap;
  import org.w3c.dom.Node;
  import org.w3c.dom.NodeList;
  
  
  public class ElementNodeCleanupHandler extends NodeCleanupHandler {
  	protected static final char DOUBLE_QUOTE = '\"'; //$NON-NLS-1$
  	protected static final String DOUBLE_QUOTES = "\"\""; //$NON-NLS-1$
  	protected static final String EMPTY_TAG_CLOSE = "/>"; //$NON-NLS-1$
  	protected static final String END_TAG_OPEN = "</"; //$NON-NLS-1$
  	protected static final char SINGLE_QUOTE = '\''; //$NON-NLS-1$
  	protected static final String SINGLE_QUOTES = "''"; //$NON-NLS-1$
  
  	/** Non-NLS strings */
  	protected static final String START_TAG_OPEN = "<"; //$NON-NLS-1$
  	protected static final String TAG_CLOSE = ">"; //$NON-NLS-1$
  
  	public Node cleanup(Node node) {
  		Node newNode = cleanupChildren(node);
  		IDOMNode renamedNode = newNode instanceof IDOMNode ? (IDOMNode) newNode : null;
  
  		// 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 (!isCommentTag(renamedNode) && !isImplicitTag(renamedNode)) {
  			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
  		renamedNode = insertMissingTags(renamedNode);
  
  		renamedNode = compressEmptyElementTag(renamedNode);
  
  		renamedNode = insertRequiredAttrs(renamedNode);
  
  		return renamedNode;
  	}
  
  	protected Node cleanupChildren(Node node) {
  		Node parentNode = node;
  
  		if (node != null) {
  			Node childNode = node.getFirstChild();
  			while (childNode != null) {
  				// get cleanup handler
  				IStructuredCleanupHandler cleanupHandler = getCleanupHandler(childNode);
  
  				// cleanup each child
  				childNode = cleanupHandler.cleanup(childNode);
  
  				// get new parent node
  				parentNode = childNode.getParentNode();
  
  				// get next child node
  				childNode = childNode.getNextSibling();
  			}
  		}
  
  		return parentNode;
  	}
  
  	private IDOMNode compressEmptyElementTag(IDOMNode node) {
  		boolean compressEmptyElementTags = getCleanupPreferences().getCompressEmptyElementTags();
  		IDOMNode newNode = node;
  
  		IStructuredDocumentRegion startTagStructuredDocumentRegion = newNode.getFirstStructuredDocumentRegion();
  		IStructuredDocumentRegion endTagStructuredDocumentRegion = newNode.getLastStructuredDocumentRegion();
  
  		if (compressEmptyElementTags && startTagStructuredDocumentRegion != endTagStructuredDocumentRegion && startTagStructuredDocumentRegion != null) {
  			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;
  	}
  
  	protected IStructuredCleanupHandler getCleanupHandler(Node node) {
  		short nodeType = node.getNodeType();
  		IStructuredCleanupHandler cleanupHandler = null;
  		switch (nodeType) {
  			case org.w3c.dom.Node.ELEMENT_NODE : {
  				cleanupHandler = new ElementNodeCleanupHandler();
  				break;
  			}
  			case org.w3c.dom.Node.TEXT_NODE : {
  				cleanupHandler = new NodeCleanupHandler();
  				break;
  			}
  			default : {
  				cleanupHandler = new NodeCleanupHandler();
  			}
  		}
  
  		// init CleanupPreferences
  		cleanupHandler.setCleanupPreferences(getCleanupPreferences());
  
  		return cleanupHandler;
  	}
  
  
  	protected ModelQuery getModelQuery(Node node) {
  		if (node.getNodeType() == Node.DOCUMENT_NODE) {
  			return ModelQueryUtil.getModelQuery((Document) node);
  		} else {
  			return ModelQueryUtil.getModelQuery(node.getOwnerDocument());
  		}
  	}
  
  	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;
  	}
  
  	private IDOMNode insertEndTag(IDOMNode node) {
  		IDOMNode newNode = node;
  		IDOMElement element = (IDOMElement) node;
  		if (element.isCommentTag())
  			return node; // do nothing
  
  		int startTagStartOffset = node.getStartOffset();
  		IDOMModel structuredModel = node.getModel();
  
  		if (isEmptyElement(element)) {
  			IStructuredDocument structuredDocument = structuredModel.getStructuredDocument();
  			IStructuredDocumentRegion startStructuredDocumentRegion = node.getStartStructuredDocumentRegion();
  			ITextRegionList regions = startStructuredDocumentRegion.getRegions();
  			ITextRegion lastRegion = regions.get(regions.size() - 1);
  			structuredDocument.replaceText(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())
  					structuredDocument.replaceText(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();
  			structuredDocument.replaceText(structuredDocument, endTagStartOffset, 0, endTag);
  		}
  
  		newNode = (IDOMNode) structuredModel.getIndexedRegion(startTagStartOffset); // save
  		// new
  		// node
  
  		return newNode;
  	}
  
  	private 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
  