/*******************************************************************************
 * Copyright (c) 2010, 2022 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     
 *******************************************************************************/
package org.eclipse.wst.xml.ui.internal.contentassist;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Vector;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
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.ITextRegionContainer;
import org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext;
import org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer;
import org.eclipse.wst.sse.ui.internal.contentassist.ContentAssistUtils;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
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.regions.DOMRegionContext;
import org.eclipse.wst.xml.ui.internal.Logger;
import org.eclipse.wst.xml.ui.internal.XMLUIMessages;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * <p>Implements the framework for making proposals in XML type documents.  Deals with
 * all the nastiness needed to figure out where in an XML type document the content
 * assist was invoked and then calls one of many abstract methods depending on the
 * area the content assist was invoked.  In this way implementers only have to worry about
 * implementing what to do in each situation while not having to deal with figuring out
 * which situation the content assist was invoked in.</p>
 * 
 * @base org.eclipse.wst.xml.ui.internal.contentassist.AbstractContentAssistProcessor
 * @see DefaultXMLCompletionProposalComputer
 */
public abstract class AbstractXMLCompletionProposalComputer implements ICompletionProposalComputer {
	/**
	 * ISSUE: this is a bit of hidden JSP knowledge that was implemented this
	 * way for expediency. Should be evolved in future to depend on
	 * "nestedContext".
	 */
	private class DOMJSPRegionContextsPrivateCopy {
		private static final String JSP_CLOSE = "JSP_CLOSE"; //$NON-NLS-1$
		private static final String JSP_COMMENT_CLOSE = "JSP_COMMENT_CLOSE"; //$NON-NLS-1$

		private static final String JSP_COMMENT_OPEN = "JSP_COMMENT_OPEN"; //$NON-NLS-1$
		private static final String JSP_DECLARATION_OPEN = "JSP_DECLARATION_OPEN"; //$NON-NLS-1$
		private static final String JSP_DIRECTIVE_CLOSE = "JSP_DIRECTIVE_CLOSE"; //$NON-NLS-1$
		private static final String JSP_DIRECTIVE_NAME = "JSP_DIRECTIVE_NAME"; //$NON-NLS-1$

		private static final String JSP_DIRECTIVE_OPEN = "JSP_DIRECTIVE_OPEN"; //$NON-NLS-1$
		private static final String JSP_EXPRESSION_OPEN = "JSP_EXPRESSION_OPEN"; //$NON-NLS-1$

		private static final String JSP_ROOT_TAG_NAME = "JSP_ROOT_TAG_NAME"; //$NON-NLS-1$

		private static final String JSP_SCRIPTLET_OPEN = "JSP_SCRIPTLET_OPEN"; //$NON-NLS-1$
	}

	private String fErrorMessage;
	private ITextViewer fTextViewer;

	public AbstractXMLCompletionProposalComputer() {
		fErrorMessage = null;
		fTextViewer = null;
	}
	
	/**
	 * <p>Return a list of proposed code completions based on the specified
	 * location within the document that corresponds to the current cursor
	 * position within the text-editor control.</p>
	 * 
	 * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#computeCompletionProposals(org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext, org.eclipse.core.runtime.IProgressMonitor)
	 */
	public List computeCompletionProposals(
			CompletionProposalInvocationContext context,
			IProgressMonitor monitor) {
		
		ITextViewer textViewer = context.getViewer();
		int documentPosition = context.getInvocationOffset();

		setErrorMessage(null);

		fTextViewer = textViewer;

		IndexedRegion treeNode = ContentAssistUtils.getNodeAt(textViewer, documentPosition);

		Node node = (Node) treeNode;
		while ((node != null) && (node.getNodeType() == Node.TEXT_NODE) && (node.getParentNode() != null)) {
			node = node.getParentNode();
		}
		IDOMNode xmlnode = (IDOMNode) node;

		ContentAssistRequest contentAssistRequest = null;

		IStructuredDocumentRegion sdRegion = getStructuredDocumentRegion(documentPosition);
		ITextRegion completionRegion = getCompletionRegion(documentPosition, node);

		String matchString = getMatchString(sdRegion, completionRegion, documentPosition);

		// Handle empty Documents
		if (completionRegion == null) {
			if (((treeNode == null) || (((Node) treeNode).getNodeType() == Node.DOCUMENT_NODE)) && (completionRegion == null) &&
					((xmlnode == null) || (xmlnode.getChildNodes() == null) || (xmlnode.getChildNodes().getLength() == 0))) {
				
				IStructuredModel sModel = null;
				try {
					if(textViewer.getDocument() instanceof IStructuredDocument) {
						sModel = StructuredModelManager.getModelManager().getModelForRead((IStructuredDocument)textViewer.getDocument());
					}
					if (sModel != null) {
						IDOMDocument docNode = ((IDOMModel) sModel).getDocument();
						contentAssistRequest = new ContentAssistRequest(docNode, docNode, sdRegion, completionRegion, documentPosition - matchString.length(), matchString.length(), null);
						addEmptyDocumentProposals(contentAssistRequest, context);
					}
				}
				finally {
					if (sModel != null) {
						sModel.releaseFromRead();
					}
				}
				if (contentAssistRequest == null) {
					Logger.logException(new IllegalStateException("problem getting model")); //$NON-NLS-1$
					return new ArrayList(0);
				}
				
				ICompletionProposal[] props = contentAssistRequest.getCompletionProposals();
				return (props != null) ? Arrays.asList(props) : new ArrayList(0);
			}
			// MASSIVE ERROR CONDITION
			Logger.logException(new IllegalStateException("completion region was null")); //$NON-NLS-1$
			setErrorMessage(XMLUIMessages.SEVERE_internal_error_occu_UI_);
			contentAssistRequest = new ContentAssistRequest((Node) treeNode, node.getParentNode(), sdRegion, completionRegion, documentPosition, 0, ""); //$NON-NLS-1$
			ICompletionProposal[] props = contentAssistRequest.getCompletionProposals();
			return (props != null) ? Arrays.asList(props) : new ArrayList(0);
		}


		// catch documents where no region can be determined
		if ((xmlnode.getNodeType() == Node.DOCUMENT_NODE) && ((completionRegion == null) ||
				(xmlnode.getChildNodes() == null) || (xmlnode.getChildNodes().getLength() == 0))) {
			
			contentAssistRequest = computeStartDocumentProposals(matchString, completionRegion,
					(IDOMNode) treeNode,xmlnode, context);
			ICompletionProposal[] props = contentAssistRequest.getCompletionProposals();
			return (props != null) ? Arrays.asList(props) : new ArrayList(0);
		}

		// compute normal proposals
		contentAssistRequest = computeCompletionProposals(matchString, completionRegion, (IDOMNode) treeNode, xmlnode, context);
		if (contentAssistRequest == null) {
			contentAssistRequest = new ContentAssistRequest((Node) treeNode, node.getParentNode(), sdRegion, completionRegion, documentPosition, 0, ""); //$NON-NLS-1$
			setErrorMessage(XMLUIMessages.Content_Assist_not_availab_UI_);
		}

		/* 
		 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=123892
		 * Only set this error message if nothing else was already set 
		 **/
		if (contentAssistRequest.getProposals().size() == 0 && getErrorMessage() == null) {
			setErrorMessage(XMLUIMessages.Content_Assist_not_availab_UI_);
		}

		ICompletionProposal[] props = contentAssistRequest.getCompletionProposals();
		return (props != null) ? Arrays.asList(props) : new ArrayList(0);
	}
	
	/**
	 * <p>Returns information about possible contexts based on the specified
	 * location within the document that corresponds to the current cursor
	 * position within the text viewer.</p>
	 * 
	 * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#computeContextInformation(org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext, org.eclipse.core.runtime.IProgressMonitor)
	 */
	public List computeContextInformation(
			CompletionProposalInvocationContext context,
			IProgressMonitor monitor) {
		
		//no default context info
		return Collections.EMPTY_LIST;
	}
	
	/**
	 * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#getErrorMessage()
	 */
	public String getErrorMessage() {
		return fErrorMessage;
	}

	/**
	 * Add proposals for attribute names
	 * 
	 * @param contentAssistRequest
	 * @param context
	 */
	protected abstract void addAttributeNameProposals(ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context);

	/**
	 * Add proposals for attribute values
	 * 
	 * @param contentAssistRequest
	 * @param context
	 */
	protected abstract void addAttributeValueProposals(ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context);

	/**
	 * Add comment proposals
	 * 
	 * @param contentAssistRequest
	 * @param context
	 */
	protected abstract void addCommentProposal(ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context);

	/**
	 * Add the proposals for a completely empty document
	 * 
	 * @param contentAssistRequest
	 * @param context
	 */
	protected abstract void addEmptyDocumentProposals(ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context);
	
	/**
	 * Add the proposals for the name in an end tag
	 * 
	 * @param contentAssistRequest
	 * @param context
	 */
	protected abstract void addEndTagNameProposals(ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context);
	
	/**
	 * Prompt for end tags to a non-empty Node that hasn't ended Handles these
	 * cases: <br>
	 * <tagOpen>| <br>
	 * <tagOpen>< |<br>
	 * <tagOpen></ |
	 * 
	 * @param contentAssistRequest
	 * @param context
	 */
	protected abstract void addEndTagProposals(ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context);
	
	/**
	 * Add entity proposals
	 * 
	 * @param contentAssistRequest
	 * @param completionRegion
	 * @param treeNode
	 * @param context
	 */
	protected abstract void addEntityProposals(ContentAssistRequest contentAssistRequest,
			ITextRegion completionRegion, IDOMNode treeNode, CompletionProposalInvocationContext context);

	/**
	 * add entity proposals
	 * 
	 * @param proposals
	 * @param map
	 * @param key
	 * @param nodeOffset
	 * @param sdRegion
	 * @param completionRegion
	 * @param context
	 */
	protected abstract void addEntityProposals(Vector proposals, Properties map, String key,
			int nodeOffset, IStructuredDocumentRegion sdRegion, ITextRegion completionRegion,
			CompletionProposalInvocationContext context);

	/**
	 * Add PCData proposals
	 * 
	 * @param nodeName
	 * @param contentAssistRequest
	 * @param context
	 */
	protected abstract void addPCDATAProposal(String nodeName, ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context);

	/**
	 * Add start document proposals
	 * 
	 * @param contentAssistRequest
	 * @param context
	 */
	protected abstract void addStartDocumentProposals(ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context);

	/**
	 * Close an unclosed start tag
	 * 
	 * @param contentAssistRequest
	 * @param context
	 */
	protected abstract void addTagCloseProposals(ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context);

	/**
	 * Add tag insertion proposals
	 * 
	 * @param contentAssistRequest
	 * @param childPosition
	 * @param context
	 */
	protected abstract void addTagInsertionProposals(ContentAssistRequest contentAssistRequest,
			int childPosition, CompletionProposalInvocationContext context);

	/**
	 * Add tag name proposals
	 * 
	 * @param contentAssistRequest
	 * @param childPosition
	 * @param context
	 */
	protected abstract void addTagNameProposals(ContentAssistRequest contentAssistRequest,
			int childPosition, CompletionProposalInvocationContext context);
	
	/**
	 * @param errorMessage the reason why computeProposals was not able to find any
	 * completions.
	 */
	protected void setErrorMessage(String errorMessage) {
		fErrorMessage = errorMessage;
	}
	
	/**
	 * <p>This does all the magic of figuring out where in the XML type document
	 * the content assist was invoked and then calling the corresponding method
	 * to add the correct proposals</p>
	 * 
	 * <p><b>NOTE: </b>if overriding be sure to make super call back to this method otherwise
	 * you will loose all of the proposals generated by this method</p>
	 * 
	 * @param matchString
	 * @param completionRegion
	 * @param treeNode
	 * @param xmlnode
	 * @param context
	 * 
	 * @return {@link ContentAssistRequest} that now has all the proposals in it
	 */
	protected ContentAssistRequest computeCompletionProposals(String matchString, ITextRegion completionRegion,
			IDOMNode treeNode, IDOMNode xmlnode, CompletionProposalInvocationContext context) {
		
		int documentPosition = context.getInvocationOffset();
		
		ContentAssistRequest contentAssistRequest = null;
		String regionType = completionRegion.getType();
		IStructuredDocumentRegion sdRegion = getStructuredDocumentRegion(documentPosition);

		// Handle the most common and best supported cases
		if ((xmlnode.getNodeType() == Node.ELEMENT_NODE) || (xmlnode.getNodeType() == Node.DOCUMENT_NODE)) {
			if (regionType == DOMRegionContext.XML_TAG_OPEN) {
				contentAssistRequest = computeTagOpenProposals(matchString, completionRegion, treeNode, xmlnode, context);
			}
			else if (regionType == DOMRegionContext.XML_TAG_NAME) {
				contentAssistRequest = computeTagNameProposals(matchString, completionRegion,
						treeNode, xmlnode, context);
			}
			else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) {
				contentAssistRequest = computeAttributeProposals(matchString, completionRegion, treeNode, xmlnode, context);
			}
			else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) {
				contentAssistRequest = computeEqualsProposals(matchString, completionRegion,
						treeNode, xmlnode, context);
			}
			else if ((regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) && (documentPosition == sdRegion.getTextEndOffset()) &&
					(sdRegion.getText(completionRegion).endsWith("\"") || sdRegion.getText(completionRegion).endsWith("\'"))) { //$NON-NLS-1$ //$NON-NLS-2$ 
				// this is for when the cursor is at the end of the closing
				// quote for an attribute..
				IDOMNode actualNode = (IDOMNode) xmlnode.getModel().getIndexedRegion(sdRegion.getStartOffset(completionRegion));
				contentAssistRequest = new ContentAssistRequest(actualNode, actualNode, sdRegion, completionRegion, documentPosition, 0, matchString);
				addTagCloseProposals(contentAssistRequest, context);
			}
			else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) {
				contentAssistRequest = computeAttributeValueProposals(matchString, completionRegion, treeNode, xmlnode, context);
			}
			else if ((regionType == DOMRegionContext.XML_TAG_CLOSE) || (regionType == DOMRegionContext.XML_EMPTY_TAG_CLOSE) ||
					(regionType.equals(DOMJSPRegionContextsPrivateCopy.JSP_DIRECTIVE_CLOSE))) {
				
				contentAssistRequest = computeTagCloseProposals(matchString, completionRegion, treeNode, xmlnode, context);
			}
			else if (regionType == DOMRegionContext.XML_END_TAG_OPEN) {
				contentAssistRequest = computeEndTagOpenProposals(matchString, completionRegion,
						treeNode, xmlnode, context);
			}
			else if ((regionType == DOMRegionContext.XML_CONTENT) || (regionType == DOMRegionContext.XML_CHAR_REFERENCE) ||
					(regionType == DOMRegionContext.XML_ENTITY_REFERENCE) || (regionType == DOMRegionContext.XML_PE_REFERENCE)) {
				
				contentAssistRequest = computeContentProposals(matchString, completionRegion,
						treeNode, xmlnode, context);
			}

			// These ITextRegion types begin DOM Elements as well and although
			// internally harder to assist,
			// text insertions directly before them can be made
			else if ((documentPosition == sdRegion.getStartOffset(completionRegion)) &&
					(regionType.equals(DOMJSPRegionContextsPrivateCopy.JSP_COMMENT_OPEN) ||
							regionType.equals(DOMJSPRegionContextsPrivateCopy.JSP_DECLARATION_OPEN) ||
							regionType.equals(DOMJSPRegionContextsPrivateCopy.JSP_DIRECTIVE_OPEN) ||
							regionType.equals(DOMJSPRegionContextsPrivateCopy.JSP_EXPRESSION_OPEN) ||
							regionType.equals(DOMJSPRegionContextsPrivateCopy.JSP_SCRIPTLET_OPEN) ||
							(regionType == DOMRegionContext.XML_DECLARATION_OPEN) ||
							(regionType == DOMRegionContext.XML_PI_OPEN) ||
							(regionType == DOMRegionContext.XML_COMMENT_OPEN) ||
							(regionType == DOMRegionContext.XML_CDATA_OPEN))) {
				
				contentAssistRequest = new ContentAssistRequest(treeNode, xmlnode.getParentNode(), sdRegion, completionRegion, documentPosition, 0, matchString);
				addTagInsertionProposals(contentAssistRequest, getElementPosition(treeNode), context);
				addStartDocumentProposals(contentAssistRequest, context);
			}
		}
		// Not a Document or Element? (odd cases go here for now)
		else if (isCloseRegion(completionRegion)) {
			contentAssistRequest = new ContentAssistRequest(treeNode, xmlnode.getParentNode(), sdRegion, completionRegion, sdRegion.getStartOffset(completionRegion) + completionRegion.getLength(), 0, matchString);
			addStartDocumentProposals(contentAssistRequest, context);
			if (documentPosition >= sdRegion.getTextEndOffset(completionRegion)) {
				addTagInsertionProposals(contentAssistRequest, getElementPosition(treeNode) + 1, context);
			}
		}
		else if ((documentPosition == sdRegion.getStartOffset(completionRegion)) &&
				(regionType.equals(DOMJSPRegionContextsPrivateCopy.JSP_COMMENT_OPEN) ||
						regionType.equals(DOMJSPRegionContextsPrivateCopy.JSP_DECLARATION_OPEN) ||
						regionType.equals(DOMJSPRegionContextsPrivateCopy.JSP_DIRECTIVE_OPEN) ||
						regionType.equals(DOMJSPRegionContextsPrivateCopy.JSP_EXPRESSION_OPEN) ||
						regionType.equals(DOMJSPRegionContextsPrivateCopy.JSP_SCRIPTLET_OPEN) ||
						(regionType == DOMRegionContext.XML_DECLARATION_OPEN) ||
						(regionType == DOMRegionContext.XML_PI_OPEN) ||
						(regionType == DOMRegionContext.XML_COMMENT_OPEN) ||
						(regionType == DOMRegionContext.XML_CDATA_OPEN))) {
			
			contentAssistRequest = new ContentAssistRequest(treeNode, xmlnode.getParentNode(), sdRegion, completionRegion, documentPosition, 0, matchString);
			addTagInsertionProposals(contentAssistRequest, getElementPosition(treeNode), context);
			addStartDocumentProposals(contentAssistRequest, context);
		}
		return contentAssistRequest;
	}
	
	/**
	 * <p>Similar to {@link #computeCompletionProposals(CompletionProposalInvocationContext, IProgressMonitor)} only specificly for
	 * attribute proposals</p>
	 * 
	 * <p>Implementers should not override this method, it is made available to implementers so that if they override
	 * {@link #computeCompletionProposals(String, ITextRegion, IDOMNode, IDOMNode, CompletionProposalInvocationContext)}
	 * they can call this method if needed</p>
	 * 
	 * @param matchString
	 * @param completionRegion
	 * @param nodeAtOffset
	 * @param node
	 * @param context
	 * @return
	 */
	protected final ContentAssistRequest computeAttributeProposals(String matchString, ITextRegion completionRegion,
			IDOMNode nodeAtOffset, IDOMNode node, CompletionProposalInvocationContext context) {
		
		int documentPosition = context.getInvocationOffset();
		ITextViewer viewer = context.getViewer();
		ContentAssistRequest contentAssistRequest = null;
		IStructuredDocumentRegion sdRegion = getStructuredDocumentRegion(documentPosition);
		// if the attribute name is selected, replace it instead of creating a new attribute
		if (documentPosition <= sdRegion.getStartOffset(completionRegion) && (viewer != null && viewer.getSelectedRange().y != (sdRegion.getEndOffset(completionRegion) - sdRegion.getStartOffset(completionRegion)))) {
			// setup to insert new attributes
			contentAssistRequest = new ContentAssistRequest(nodeAtOffset, node, sdRegion, completionRegion, documentPosition, 0, matchString);
		}
		else {
			// Setup to replace an existing attribute name
			contentAssistRequest = new ContentAssistRequest(nodeAtOffset, node, sdRegion, completionRegion, sdRegion.getStartOffset(completionRegion), completionRegion.getTextLength(), matchString);
		}
		addAttributeNameProposals(contentAssistRequest, context);
		contentAssistRequest.setReplacementBeginPosition(documentPosition);
		contentAssistRequest.setReplacementLength(0);
		if ((node.getFirstStructuredDocumentRegion() != null) && (!node.getFirstStructuredDocumentRegion().isEnded())) {
			addTagCloseProposals(contentAssistRequest, context);
		}
		return contentAssistRequest;
	}
	
	/**
	 * <p>this is the position the cursor should be in after the proposal is
	 * applied</p>
	 * 
	 * @param proposedText
	 * @return the position the cursor should be in after the proposal is
	 *         applied
	 */
	protected static int getCursorPositionForProposedText(String proposedText) {
		int cursorAdjustment;
		cursorAdjustment = proposedText.indexOf("\"\"") + 1; //$NON-NLS-1$
		// otherwise, after the first tag
		if (cursorAdjustment == 0) {
			cursorAdjustment = proposedText.indexOf('>') + 1;
		}
		if (cursorAdjustment == 0) {
			cursorAdjustment = proposedText.length() + 1;
		}

		return cursorAdjustment;
	}
	
	/**
	 * <p>helpful utility method for determining if one string starts with another one.
	 * This is case insensitive. If either are null then result is <code>true</code></p>
	 * 
	 * @param aString the string to check to see if it starts with the given prefix
	 * @param prefix check that the given string starts with this prefix
	 * 
	 * @return <code>true</code> if the given string starts with the given prefix,
	 * <code>false</code> otherwise
	 */
	protected static boolean beginsWith(String aString, String prefix) {
		if ((aString == null) || (prefix == null)) {
			return true;
		}
		return aString.toLowerCase().startsWith(prefix.toLowerCase());
	}

	private ContentAssistRequest computeAttributeValueProposals(String matchString, ITextRegion completionRegion,
			IDOMNode nodeAtOffset, IDOMNode node, CompletionProposalInvocationContext context) {
		
		int documentPosition = context.getInvocationOffset();
		
		ContentAssistRequest contentAssistRequest = null;
		IStructuredDocumentRegion sdRegion = getStructuredDocumentRegion(documentPosition);
		if ((documentPosition > sdRegion.getStartOffset(completionRegion) + completionRegion.getTextLength()) && (sdRegion.getStartOffset(completionRegion) + completionRegion.getTextLength() != sdRegion.getStartOffset(completionRegion) + completionRegion.getLength())) {
			// setup to add a new attribute at the documentPosition
			IDOMNode actualNode = (IDOMNode) node.getModel().getIndexedRegion(sdRegion.getStartOffset(completionRegion));
			contentAssistRequest = new ContentAssistRequest(actualNode, actualNode, sdRegion, completionRegion, documentPosition, 0, matchString);
			addAttributeNameProposals(contentAssistRequest, context);
			if ((actualNode.getFirstStructuredDocumentRegion() != null) && !actualNode.getFirstStructuredDocumentRegion().isEnded()) {
				addTagCloseProposals(contentAssistRequest, context);
			}
		}
		else {
			// setup to replace the existing value
			if (!nodeAtOffset.getFirstStructuredDocumentRegion().isEnded() && (documentPosition < sdRegion.getStartOffset(completionRegion))) {
				// if the IStructuredDocumentRegion isn't closed and the
				// cursor is in front of the value, add
				contentAssistRequest = new ContentAssistRequest(nodeAtOffset, node, sdRegion, completionRegion, documentPosition, 0, matchString);
				addAttributeNameProposals(contentAssistRequest, context);
			}
			else {
				int replaceLength = completionRegion.getTextLength();
				
				//if container region, be sure replace length is only the attribute value region not the entire container
				if(completionRegion instanceof ITextRegionContainer){
					ITextRegion openRegion = ((ITextRegionContainer) completionRegion).getFirstRegion();
					ITextRegion closeRegion = ((ITextRegionContainer) completionRegion).getLastRegion();
					
					/*
					 * check to see if the container is opened the same way its closed.
					 * Such as:
					 * <img src=' '
					 * But not:
					 * <img src='
					 * 
					 * </body>
					 * </html>
					 * In the latter case we only want to replace the opening text of the container
					 * Admittedly crude test, but effective.
					 */
					if(openRegion.getType() != closeRegion.getType()) {
						replaceLength = openRegion.getTextLength();
					}
				} 
				
				contentAssistRequest = new ContentAssistRequest(nodeAtOffset, node, sdRegion,
						completionRegion, sdRegion.getStartOffset(completionRegion),
						replaceLength, matchString);
				
				addAttributeValueProposals(contentAssistRequest, context);
			}
		}
		return contentAssistRequest;
	}

	private ContentAssistRequest computeContentProposals(String matchString, ITextRegion completionRegion,
			IDOMNode nodeAtOffset, IDOMNode node, CompletionProposalInvocationContext context) {
		
		int documentPosition = context.getInvocationOffset();
		ContentAssistRequest contentAssistRequest = null;

		// setup to add children at the content node's position
		contentAssistRequest = new ContentAssistRequest(nodeAtOffset, node, getStructuredDocumentRegion(documentPosition), completionRegion, documentPosition - matchString.length(), matchString.length(), matchString);
		if ((node != null) && (node.getNodeType() == Node.DOCUMENT_NODE) && (((Document) node).getDoctype() == null)) {
			addStartDocumentProposals(contentAssistRequest, context);
		}
		addTagInsertionProposals(contentAssistRequest, getElementPosition(nodeAtOffset), context);
		if (node.getNodeType() != Node.DOCUMENT_NODE) {
			addEndTagProposals(contentAssistRequest, context);
		}
		// entities?
		addEntityProposals(contentAssistRequest, completionRegion, node, context);
		// addEntityProposals(contentAssistRequest);
		return contentAssistRequest;
	}

	private ContentAssistRequest computeEndTagOpenProposals(String matchString, ITextRegion completionRegion,
			IDOMNode nodeAtOffset, IDOMNode node, CompletionProposalInvocationContext context) {
		
		int documentPosition = context.getInvocationOffset();
		ContentAssistRequest contentAssistRequest = null;
		IStructuredDocumentRegion sdRegion = getStructuredDocumentRegion(documentPosition);
		int completionRegionStart = sdRegion.getStartOffset(completionRegion);
		int completionRegionLength = completionRegion.getLength();
		IStructuredDocumentRegion sdRegionAtCompletionOffset = node.getStructuredDocument().getRegionAtCharacterOffset(completionRegionStart + completionRegionLength);
		ITextRegion regionAtEndOfCompletion = sdRegionAtCompletionOffset.getRegionAtCharacterOffset(completionRegionStart + completionRegionLength);

		if ((documentPosition != completionRegionStart) && (regionAtEndOfCompletion != null) && (regionAtEndOfCompletion.getType() == DOMRegionContext.XML_TAG_NAME)) {
			ITextRegion nameRegion = regionAtEndOfCompletion;
			contentAssistRequest = new ContentAssistRequest(nodeAtOffset, nodeAtOffset.getParentNode(), sdRegion, completionRegion, sdRegion.getStartOffset(nameRegion), nameRegion.getTextLength(), matchString);
		}
		else {
			if (nodeAtOffset.getFirstStructuredDocumentRegion() == sdRegion) {
				// abnormal case, this unmatched end tag will be a sibling
				contentAssistRequest = new ContentAssistRequest(nodeAtOffset, nodeAtOffset.getParentNode(), sdRegion, completionRegion, documentPosition, 0, matchString);
			}
			else {
				// normal case, this end tag is the parent
				contentAssistRequest = new ContentAssistRequest(nodeAtOffset, nodeAtOffset, sdRegion, completionRegion, documentPosition, 0, matchString);
			}
		}
		// if (documentPosition >= sdRegion.getStartOffset(completionRegion) +
		// completionRegion.getTextLength())
		addEndTagProposals(contentAssistRequest, context);
		// else
		if (completionRegionStart == documentPosition) {
			// positioned at start of end tag
			addTagInsertionProposals(contentAssistRequest, node.getChildNodes().getLength(), context);
		}
		return contentAssistRequest;
	}


	private ContentAssistRequest computeEqualsProposals(String matchString, ITextRegion completionRegion,
			IDOMNode nodeAtOffset, IDOMNode node, CompletionProposalInvocationContext context) {
		
		int documentPosition = context.getInvocationOffset();
		ContentAssistRequest contentAssistRequest = null;
		IStructuredDocumentRegion sdRegion = getStructuredDocumentRegion(documentPosition);
		ITextRegion valueRegion = node.getStartStructuredDocumentRegion().getRegionAtCharacterOffset(sdRegion.getStartOffset(completionRegion) + completionRegion.getLength());
		if ((valueRegion != null) && (valueRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) && (sdRegion.getStartOffset(valueRegion) <= documentPosition)) {
			// replace the adjacent attribute value
			contentAssistRequest = new ContentAssistRequest(nodeAtOffset, node, sdRegion, valueRegion, sdRegion.getStartOffset(valueRegion), valueRegion.getTextLength(), matchString);
		}
		else {
			// append an attribute value after the '='
			contentAssistRequest = new ContentAssistRequest(nodeAtOffset, node, sdRegion, completionRegion, documentPosition, 0, matchString);
		}
		addAttributeValueProposals(contentAssistRequest, context);
		return contentAssistRequest;
	}

	private ContentAssistRequest computeStartDocumentProposals(String matchString, ITextRegion completionRegion,
			IDOMNode nodeAtOffset, IDOMNode node, CompletionProposalInvocationContext context) {
		
		int documentPosition = context.getInvocationOffset();
		
		// setup for a non-empty document, but one that hasn't been formally
		// started
		ContentAssistRequest contentAssistRequest = null;
		contentAssistRequest = new ContentAssistRequest(nodeAtOffset, node,
				getStructuredDocumentRegion(documentPosition), completionRegion,
				documentPosition, 0, matchString);
		addStartDocumentProposals(contentAssistRequest, context);
		return contentAssistRequest;
	}

	private ContentAssistRequest computeTagCloseProposals(String matchString, ITextRegion completionRegion,
			IDOMNode nodeAtOffset, IDOMNode node, CompletionProposalInvocationContext context) {
		
		int documentPosition = context.getInvocationOffset();
		ContentAssistRequest contentAssistRequest = null;
		IStructuredDocumentRegion sdRegion = getStructuredDocumentRegion(documentPosition);

		if ((node.getNodeType() == Node.DOCUMENT_NODE) || (documentPosition >= sdRegion.getEndOffset())) {
			// this is a content request as the documentPosition is AFTER the
			// end of the closing region
			if ((node == nodeAtOffset) && (node.getParentNode() != null)) {
				node = (IDOMNode) node.getParentNode();
			}
			contentAssistRequest = new ContentAssistRequest(nodeAtOffset, node, sdRegion, completionRegion, documentPosition, 0, matchString);
			addTagInsertionProposals(contentAssistRequest, getElementPosition(nodeAtOffset), context);
			if ((node.getNodeType() != Node.DOCUMENT_NODE) && (node.getEndStructuredDocumentRegion() == null)) {
				addEndTagProposals(contentAssistRequest, context);
			}
		}
		else {
			// at the start of the tag's close or within it
			ITextRegion closeRegion = sdRegion.getLastRegion();
			boolean insideTag = !sdRegion.isEnded() || (documentPosition <= sdRegion.getStartOffset(closeRegion));
			if (insideTag) {
				// this is a request for completions within a tag
				contentAssistRequest = new ContentAssistRequest(nodeAtOffset, node, sdRegion, completionRegion, documentPosition, 0, matchString);
				if ((node.getNodeType() != Node.DOCUMENT_NODE) && (node.getEndStructuredDocumentRegion() != null)) {
					addTagCloseProposals(contentAssistRequest, context);
				}
				if (sdRegion == nodeAtOffset.getFirstStructuredDocumentRegion()) {
					contentAssistRequest.setReplacementBeginPosition(documentPosition);
					contentAssistRequest.setReplacementLength(0);
					addAttributeNameProposals(contentAssistRequest, context);
				}
			}
		}
		return contentAssistRequest;
	}

	private ContentAssistRequest computeTagNameProposals(String matchString, ITextRegion completionRegion,
			IDOMNode nodeAtOffset, IDOMNode node, CompletionProposalInvocationContext context) {
		
		int documentPosition = context.getInvocationOffset();
		ContentAssistRequest contentAssistRequest = null;
		IStructuredDocumentRegion sdRegion = getStructuredDocumentRegion(documentPosition);

		if (sdRegion != nodeAtOffset.getFirstStructuredDocumentRegion()) {
			// completing the *first* tag in "<tagname1 |<tagname2"
			IDOMNode actualNode = (IDOMNode) node.getModel().getIndexedRegion(sdRegion.getStartOffset(completionRegion));
			if (actualNode != null) {
				if (actualNode.getFirstStructuredDocumentRegion() == sdRegion) {
					// start tag
					if (documentPosition > sdRegion.getStartOffset(completionRegion) + completionRegion.getLength()) {
						// it's attributes
						contentAssistRequest = new ContentAssistRequest(actualNode, actualNode, sdRegion, completionRegion, documentPosition - matchString.length(), matchString.length(), matchString);
						if (node.getStructuredDocument().getRegionAtCharacterOffset(sdRegion.getStartOffset(completionRegion) - 1).getRegionAtCharacterOffset(sdRegion.getStartOffset(completionRegion) - 1).getType() == DOMRegionContext.XML_TAG_OPEN) {
							addAttributeNameProposals(contentAssistRequest, context);
						}
						addTagCloseProposals(contentAssistRequest, context);
					}
					else {
						// it's name
						contentAssistRequest = new ContentAssistRequest(actualNode, actualNode.getParentNode(),
								sdRegion, completionRegion, documentPosition - matchString.length(),
								matchString.length(), matchString);
						addTagNameProposals(contentAssistRequest, getElementPosition(actualNode),
								context);
					}
				}
				else {
					if (documentPosition >= sdRegion.getStartOffset(completionRegion) + completionRegion.getLength()) {
						// insert name
						contentAssistRequest = new ContentAssistRequest(actualNode, actualNode.getParentNode(),
								sdRegion, completionRegion, documentPosition, 0, matchString);
					}
					else {
						// replace name
						contentAssistRequest = new ContentAssistRequest(actualNode, actualNode.getParentNode(),
								sdRegion, completionRegion, sdRegion.getStartOffset(completionRegion),
								completionRegion.getTextLength(), matchString);
					}
					addEndTagNameProposals(contentAssistRequest, context);
				}
			}
		}
		else {
			if (documentPosition > sdRegion.getStartOffset(completionRegion) + completionRegion.getTextLength()) {
				// unclosed tag with only a name; should prompt for attributes
				// and a close instead
				contentAssistRequest = new ContentAssistRequest(nodeAtOffset, node, sdRegion, completionRegion,
						documentPosition - matchString.length(), matchString.length(), matchString);
				addAttributeNameProposals(contentAssistRequest, context);
				addTagCloseProposals(contentAssistRequest, context);
			}
			else {
				if (sdRegion.getRegions().get(0).getType() != DOMRegionContext.XML_END_TAG_OPEN) {
					int replaceLength = documentPosition - sdRegion.getStartOffset(completionRegion);
					contentAssistRequest = new ContentAssistRequest(node, node.getParentNode(), sdRegion,
							completionRegion, sdRegion.getStartOffset(completionRegion), replaceLength, matchString);
					addTagNameProposals(contentAssistRequest, getElementPosition(nodeAtOffset), context);
				}
				else {
					IDOMNode actualNode = (IDOMNode) node.getModel().getIndexedRegion(documentPosition);
					if (actualNode != null) {
						if (documentPosition >= sdRegion.getStartOffset(completionRegion) + completionRegion.getTextLength()) {
							contentAssistRequest = new ContentAssistRequest(actualNode, actualNode.getParentNode(),
									sdRegion, completionRegion, documentPosition, 0, matchString);
						}
						else {
							contentAssistRequest = new ContentAssistRequest(actualNode, actualNode.getParentNode(),
									sdRegion, completionRegion, sdRegion.getStartOffset(completionRegion),
									completionRegion.getTextLength(), matchString);
						}
						addEndTagNameProposals(contentAssistRequest, context);
					}
				}
			}
		}
		return contentAssistRequest;
	}

	private ContentAssistRequest computeTagOpenProposals(String matchString, ITextRegion completionRegion,
			IDOMNode nodeAtOffset, IDOMNode node, CompletionProposalInvocationContext context) {
		
		int documentPosition = context.getInvocationOffset();
		ContentAssistRequest contentAssistRequest = null;
		IStructuredDocumentRegion sdRegion = getStructuredDocumentRegion(documentPosition);
		if (sdRegion != nodeAtOffset.getFirstStructuredDocumentRegion() || sdRegion.getPrevious() != null && sdRegion.getPrevious().getLastRegion().getType() == DOMRegionContext.XML_TAG_OPEN) {
			// completing the *first* XML_TAG_OPEN in "<<tagname"
			IDOMNode actualNode = (IDOMNode) node.getModel().getIndexedRegion(sdRegion.getStartOffset(completionRegion));
			if (actualNode != null) {
				if(sdRegion.getFirstRegion().getType() == DOMRegionContext.XML_END_TAG_OPEN) {
					contentAssistRequest = new ContentAssistRequest(actualNode, actualNode, sdRegion, completionRegion, documentPosition, 0, matchString);
					if(actualNode.hasChildNodes())
						addTagNameProposals(contentAssistRequest,
								getElementPosition(actualNode.getLastChild()), context);
					else
						addTagNameProposals(contentAssistRequest, 0, context);
				}
				else {
					contentAssistRequest = new ContentAssistRequest(actualNode, actualNode.getParentNode(), sdRegion, completionRegion, documentPosition, 0, matchString);
					addTagNameProposals(contentAssistRequest, getElementPosition(actualNode), context);
				}
				addEndTagProposals(contentAssistRequest, context); // (pa) 220850
			}
		}
		else {
			if (documentPosition == sdRegion.getStartOffset(completionRegion)) {
				if (node.getNodeType() == Node.ELEMENT_NODE) {
					// at the start of an existing tag, right before the '<'
					contentAssistRequest = new ContentAssistRequest(nodeAtOffset, node.getParentNode(), sdRegion,
							completionRegion, documentPosition, 0, matchString);
					addTagInsertionProposals(contentAssistRequest, getElementPosition(nodeAtOffset), context);
					addEndTagProposals(contentAssistRequest, context);
				}
				else if (node.getNodeType() == Node.DOCUMENT_NODE) {
					// at the opening of the VERY first tag with a '<'
					contentAssistRequest = new ContentAssistRequest(nodeAtOffset, node.getParentNode(), sdRegion,
							completionRegion, sdRegion.getStartOffset(completionRegion), completionRegion.getTextLength(), matchString);
					addStartDocumentProposals(contentAssistRequest, context);
				}
			}
			else {
				// within the white space
				ITextRegion name = getNameRegion(node.getStartStructuredDocumentRegion());
				// (pa) ITextRegion refactor
				// if (name != null && name.containsOffset(documentPosition))
				// {
				if ((name != null) &&
						((sdRegion.getStartOffset(name) <= documentPosition) && (sdRegion.getEndOffset(name) >= documentPosition)) &&
						(sdRegion.getLastRegion().getType() == DOMRegionContext.XML_TAG_CLOSE || sdRegion.getLastRegion().getType() == DOMRegionContext.XML_EMPTY_TAG_CLOSE)){ 
					
					// replace the existing name
					contentAssistRequest = new ContentAssistRequest(node, node.getParentNode(), sdRegion, completionRegion, sdRegion.getStartOffset(name), name.getTextLength(), matchString);
				}
				else {
					// insert a valid new name, or possibly an end tag
					contentAssistRequest = new ContentAssistRequest(nodeAtOffset, ((Node) nodeAtOffset).getParentNode(), sdRegion, completionRegion, documentPosition, 0, matchString);
					addEndTagProposals(contentAssistRequest, context);
					contentAssistRequest.setReplacementBeginPosition(documentPosition);
					contentAssistRequest.setReplacementLength(0);
				}
				addTagNameProposals(contentAssistRequest, getElementPosition(nodeAtOffset), context);
			}
		}
		return contentAssistRequest;
	}

	private ITextRegion getCompletionRegion(int offset, IStructuredDocumentRegion sdRegion) {
		ITextRegion region = sdRegion.getRegionAtCharacterOffset(offset);
		if (region == null) {
			return null;
		}

		if (sdRegion.getStartOffset(region) == offset) {
			// The offset is at the beginning of the region
			if ((sdRegion.getStartOffset(region) == sdRegion.getStartOffset()) && (sdRegion.getPrevious() != null) && (!sdRegion.getPrevious().isEnded())) {
				// Is the region also the start of the node? If so, the
				// previous IStructuredDocumentRegion is
				// where to look for a useful region.
				region = sdRegion.getPrevious().getRegionAtCharacterOffset(offset - 1);
			}
			else {
				// Is there no separating whitespace from the previous region?
				// If not,
				// then that region is the important one
				ITextRegion previousRegion = sdRegion.getRegionAtCharacterOffset(offset - 1);
				if ((previousRegion != null) && (previousRegion != region) && (previousRegion.getTextLength() == previousRegion.getLength())) {
					region = previousRegion;
				}
			}
		}
		else {
			// The offset is NOT at the beginning of the region
			if ((region.getType() != DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) &&(offset > sdRegion.getStartOffset(region) + region.getTextLength())) { //attached XML_TAG_ATTRIBUTE_EQUALS filter due to #bug219992
				// Is the offset within the whitespace after the text in this
				// region?
				// If so, use the next region
				ITextRegion nextRegion = sdRegion.getRegionAtCharacterOffset(sdRegion.getStartOffset(region) + region.getLength());
				if (nextRegion != null) {
					region = nextRegion;
				}
			}
			else {
				// Is the offset within the important text for this region?
				// If so, then we've already got the right one.
			}
		}

		// valid WHITE_SPACE region handler (#179924)
		if ((region != null) && (region.getType() == DOMRegionContext.WHITE_SPACE)) {
			ITextRegion previousRegion = sdRegion.getRegionAtCharacterOffset(sdRegion.getStartOffset(region) - 1);
			if (previousRegion != null) {
				region = previousRegion;
			}
		}

		return region;
	}

	/**
	 * Return the region whose content's require completion. This is something
	 * of a misnomer as sometimes the user wants to be prompted for contents
	 * of a non-existant ITextRegion, such as for enumerated attribute values
	 * following an '=' sign.
	 */
	private ITextRegion getCompletionRegion(int documentPosition, Node domnode) {
		if (domnode == null) {
			return null;
		}

		ITextRegion region = null;
		int offset = documentPosition;
		IStructuredDocumentRegion flatNode = null;
		IDOMNode node = (IDOMNode) domnode;

		if (node.getNodeType() == Node.DOCUMENT_NODE) {
			if (node.getStructuredDocument().getLength() == 0) {
				return null;
			}
			ITextRegion result = node.getStructuredDocument().getRegionAtCharacterOffset(offset).getRegionAtCharacterOffset(offset);
			while (result == null) {
				offset--;
				result = node.getStructuredDocument().getRegionAtCharacterOffset(offset).getRegionAtCharacterOffset(offset);
			}
			return result;
		}

		IStructuredDocumentRegion startTag = node.getStartStructuredDocumentRegion();
		IStructuredDocumentRegion endTag = node.getEndStructuredDocumentRegion();

		// Determine if the offset is within the start
		// IStructuredDocumentRegion, end IStructuredDocumentRegion, or
		// somewhere within the Node's XML content.
		if ((startTag != null) && (startTag.getStartOffset() <= offset) && (offset < startTag.getStartOffset() + startTag.getLength())) {
			flatNode = startTag;
		}
		else if ((endTag != null) && (endTag.getStartOffset() <= offset) && (offset < endTag.getStartOffset() + endTag.getLength())) {
			flatNode = endTag;
		}

		if (flatNode != null) {
			// the offset is definitely within the start or end tag, continue
			// on and find the region
			region = getCompletionRegion(offset, flatNode);
		}
		else {
			// the docPosition is neither within the start nor the end, so it
			// must be content
			flatNode = node.getStructuredDocument().getRegionAtCharacterOffset(offset);
			// (pa) ITextRegion refactor
			// if (flatNode.contains(documentPosition)) {
			if ((flatNode.getStartOffset() <= documentPosition) && (flatNode.getEndOffset() >= documentPosition)) {
				// we're interesting in completing/extending the previous
				// IStructuredDocumentRegion if the current
				// IStructuredDocumentRegion isn't plain content or if it's
				// preceded by an orphan '<'
				if ((offset == flatNode.getStartOffset()) &&
						(flatNode.getPrevious() != null) &&
						(((flatNode.getRegionAtCharacterOffset(documentPosition) != null) &&
								(flatNode.getRegionAtCharacterOffset(documentPosition).getType() != DOMRegionContext.XML_CONTENT)) ||
								(flatNode.getPrevious().getLastRegion().getType() == DOMRegionContext.XML_TAG_OPEN) ||
								(flatNode.getPrevious().getLastRegion().getType() == DOMRegionContext.XML_END_TAG_OPEN))) {
					
					// Is the region also the start of the node? If so, the
					// previous IStructuredDocumentRegion is
					// where to look for a useful region.
					region = flatNode.getPrevious().getLastRegion();
				}
				else if (flatNode.getEndOffset() == documentPosition) {
					region = flatNode.getLastRegion();
				}
				else {
					region = flatNode.getFirstRegion();
				}
			}
			else {
				// catch end of document positions where the docPosition isn't
				// in a IStructuredDocumentRegion
				region = flatNode.getLastRegion();
			}
		}

		return region;
	}

	private int getElementPosition(Node child) {
		Node parent = child.getParentNode();
		if (parent == null) {
			return 0;
		}

		NodeList children = parent.getChildNodes();
		if (children == null) {
			return 0;
		}
		int count = 0;

		for (int i = 0; i < children.getLength(); i++) {
			if (children.item(i) == child) {
				return count;
			}
			else {
				// if (children.item(i).getNodeType() == Node.ELEMENT_NODE)
				count++;
			}
		}
		return 0;
	}

	private String getMatchString(IStructuredDocumentRegion parent, ITextRegion aRegion, int offset) {
		if ((aRegion == null) || isCloseRegion(aRegion)) {
			return ""; //$NON-NLS-1$
		}
		String matchString = null;
		String regionType = aRegion.getType();
		if ((regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) || (regionType == DOMRegionContext.XML_TAG_OPEN) || (offset > parent.getStartOffset(aRegion) + aRegion.getTextLength())) {
			matchString = ""; //$NON-NLS-1$
		}
		else if (regionType == DOMRegionContext.XML_CONTENT) {
			int start = offset;
			int end = offset;
			IDocument document = parent.getParentDocument();
			try {
				// rewind to break the "word"
				while (start > 0 && !Character.isWhitespace(document.getChar(start - 1)) && document.getChar(start - 1) != '>') {
					start--;
				}
				matchString = document.get(start, end - start);
			}
			catch (BadLocationException e) {
				matchString = ""; //$NON-NLS-1$
				Logger.logException(e);
			}
		}
		else {
			if ((parent.getText(aRegion).length() > 0) && (parent.getStartOffset(aRegion) < offset)) {
				matchString = parent.getText(aRegion).substring(0, offset - parent.getStartOffset(aRegion));
			}
			else {
				matchString = ""; //$NON-NLS-1$
			}
		}
		return matchString;
	}

	private ITextRegion getNameRegion(IStructuredDocumentRegion flatNode) {
		if (flatNode == null) {
			return null;
		}
		Iterator regionList = flatNode.getRegions().iterator();
		while (regionList.hasNext()) {
			ITextRegion region = (ITextRegion) regionList.next();
			if (isNameRegion(region)) {
				return region;
			}
		}
		return null;
	}

	private boolean isCloseRegion(ITextRegion region) {
		String type = region.getType();
		return ((type == DOMRegionContext.XML_PI_CLOSE) ||
				(type == DOMRegionContext.XML_TAG_CLOSE) ||
				(type == DOMRegionContext.XML_EMPTY_TAG_CLOSE) ||
				(type == DOMRegionContext.XML_CDATA_CLOSE) ||
				(type == DOMRegionContext.XML_COMMENT_CLOSE) ||
				(type == DOMRegionContext.XML_ATTLIST_DECL_CLOSE) ||
				(type == DOMRegionContext.XML_ELEMENT_DECL_CLOSE) ||
				(type == DOMRegionContext.XML_DOCTYPE_DECLARATION_CLOSE) ||
				(type == DOMJSPRegionContextsPrivateCopy.JSP_CLOSE) ||
				(type == DOMJSPRegionContextsPrivateCopy.JSP_COMMENT_CLOSE) ||
				(type.equals(DOMJSPRegionContextsPrivateCopy.JSP_DIRECTIVE_CLOSE)) ||
				(type == DOMRegionContext.XML_DECLARATION_CLOSE));
	}

	private boolean isNameRegion(ITextRegion region) {
		String type = region.getType();
		return ((type == DOMRegionContext.XML_TAG_NAME) ||
				(type == DOMJSPRegionContextsPrivateCopy.JSP_DIRECTIVE_NAME) ||
				(type == DOMRegionContext.XML_ELEMENT_DECL_NAME) ||
				(type == DOMRegionContext.XML_DOCTYPE_NAME) ||
				(type == DOMRegionContext.XML_ATTLIST_DECL_NAME) ||
				(type == DOMJSPRegionContextsPrivateCopy.JSP_ROOT_TAG_NAME) ||
				type.equals(DOMJSPRegionContextsPrivateCopy.JSP_DIRECTIVE_NAME));
	}
	
	/**
	 * StructuredTextViewer must be set before using this.
	 */
	private IStructuredDocumentRegion getStructuredDocumentRegion(int pos) {
		return ContentAssistUtils.getStructuredDocumentRegion(fTextViewer, pos);
	}
}
