Bug 415980 - HTML5 attribute validator marks ng-app AngularJS attributes as undefined

A new preference is created for the pattern of attribute names to be ignored by HTMLAttributeValidator
Web->HTML Files->Validation preference page is modified due to allow the ignored attribute names to be shown/edited by users.
New Quick-fix is added due to allow the users to add attribute names and attribute name patterns to the HTML Attribute Validator
ignore list.

Signed-off-by: Victor V Rubezhny <vrubezhny@exadel.com>
diff --git a/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceInitializer.java b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceInitializer.java
index 6eb852a..f3a4b14 100644
--- a/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceInitializer.java
+++ b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceInitializer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2012 IBM Corporation and others.
+ * Copyright (c) 2005, 2013 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
@@ -72,6 +72,10 @@
 	 * @param node the Eclipse preference node
 	 */
 	private void initializeValidationPreferences(IEclipsePreferences node) {
+		// Ignored Attribute names
+		node.putBoolean(HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES, HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES_DEFAULT);
+		node.put(HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE, HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT);
+		
 		// Attributes
 		node.putInt(HTMLCorePreferenceNames.ATTRIBUTE_UNDEFINED_NAME, ValidationMessage.WARNING);
 		node.putInt(HTMLCorePreferenceNames.ATTRIBUTE_UNDEFINED_VALUE, ValidationMessage.WARNING);
diff --git a/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceNames.java b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceNames.java
index fe7fb60..2b9e70e 100644
--- a/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceNames.java
+++ b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/preferences/HTMLCorePreferenceNames.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2011 IBM Corporation and others.
+ * Copyright (c) 2005, 2013 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
@@ -238,7 +238,23 @@
 	 */
 	public static final int UPPER = 2;
 	
+	/**
+	 * Default value for the preference #IGNORE_ATTRIBUTE_NAMES
+	 * 
+	 * @see #IGNORE_ATTRIBUTE_NAMES
+	 */
+	public static final boolean IGNORE_ATTRIBUTE_NAMES_DEFAULT = false;
+
+	/**
+	 * Default value for the preference #ATTRIBUTE_NAMES_TO_IGNORE
+	 * 
+	 * @see #ATTRIBUTE_NAMES_TO_IGNORE
+	 */
+	public static final String ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT = ""; //$NON-NLS-1$
+	
 	public static final String USE_PROJECT_SETTINGS = "use-project-settings";//$NON-NLS-1$
+	public static final String IGNORE_ATTRIBUTE_NAMES = "ignoreAttrNames";//$NON-NLS-1$
+	public static final String ATTRIBUTE_NAMES_TO_IGNORE = "attrNamesToIgnore";//$NON-NLS-1$
 	
 	public static final String ATTRIBUTE_UNDEFINED_NAME = "attrUndefName";//$NON-NLS-1$
 	public static final String ATTRIBUTE_UNDEFINED_VALUE = "attrUndefValue";//$NON-NLS-1$
@@ -252,7 +268,6 @@
 	public static final String ATTRIBUTE_OBSOLETE_NAME = "attrObsoleteName";//$NON-NLS-1$
 	public static final String ATTRIBUTE_VALUE_EQUALS_MISSING = "attrValueEqualsMissing";//$NON-NLS-1$
 	
-	
 	public static final String ELEM_UNKNOWN_NAME = "elemUnknownName";//$NON-NLS-1$
 	public static final String ELEM_INVALID_NAME = "elemInvalidName";//$NON-NLS-1$
 	public static final String ELEM_START_INVALID_CASE = "elemStartInvalidCase";//$NON-NLS-1$
diff --git a/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/validate/HTMLAttributeValidator.java b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/validate/HTMLAttributeValidator.java
index c4d794f..bf68297 100644
--- a/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/validate/HTMLAttributeValidator.java
+++ b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/validate/HTMLAttributeValidator.java
@@ -11,14 +11,29 @@
 package org.eclipse.wst.html.core.internal.validate;
 
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
 
+import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ProjectScope;
 import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IAdaptable;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.DefaultScope;
+import org.eclipse.core.runtime.preferences.IPreferencesService;
+import org.eclipse.core.runtime.preferences.IScopeContext;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.wst.html.core.internal.HTMLCorePlugin;
 import org.eclipse.wst.html.core.internal.document.HTMLDocumentTypeConstants;
+import org.eclipse.wst.html.core.internal.preferences.HTMLCorePreferenceNames;
 import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier;
 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
@@ -49,6 +64,9 @@
 	private static final char SINGLE_QUOTE = '\'';
 	private static final char DOUBLE_QUOTE = '\"';
 
+	private IPreferencesService fPreferenceService;
+	private static Map fIgnorePatterns = new HashMap(); // A storage for ignore patterns (instances of StringMatcher)
+
 	// HTML(5) data attributes
 	private static final String ATTR_NAME_DATA = "data-"; //$NON-NLS-1$
 	private static final int ATTR_NAME_DATA_LENGTH = ATTR_NAME_DATA.length();
@@ -63,6 +81,7 @@
 	 */
 	public HTMLAttributeValidator() {
 		super();
+		fPreferenceService = Platform.getPreferencesService();
 	}
 
 	/**
@@ -153,11 +172,15 @@
 			}
 			
 			if (adec == null) {
-				if ((attrName.startsWith(ATTR_NAME_DATA) && attrName.length() > ATTR_NAME_DATA_LENGTH) || (attrName.startsWith(ATTR_NAME_USER_AGENT_FEATURE) && attrName.length() > ATTR_NAME_USER_AGENT_FEATURE_LENGTH)) {
-					DocumentTypeAdapter documentTypeAdapter = (DocumentTypeAdapter) ((INodeNotifier) target.getOwnerDocument()).getAdapterFor(DocumentTypeAdapter.class);
-					if (documentTypeAdapter != null && documentTypeAdapter.hasFeature(HTMLDocumentTypeConstants.HTML5))
+				if ((attrName.startsWith(ATTR_NAME_DATA) && attrName.length() > ATTR_NAME_DATA_LENGTH) || 
+						(attrName.startsWith(ATTR_NAME_USER_AGENT_FEATURE) && attrName.length() > ATTR_NAME_USER_AGENT_FEATURE_LENGTH)) {
+					if (isHTML5(target))
 						continue;
-				}
+				}		
+				// Check for user-defined exclusions for HTML5 attribute names
+				if (!shouldValidateAttributeName(target, attrName)) 
+					continue;
+
 				// No attr declaration was found. That is, the attr name is
 				// undefined.
 				// but not regard it as undefined name if it includes nested
@@ -333,4 +356,60 @@
 		return (c == SINGLE_QUOTE) || (c == DOUBLE_QUOTE);
 	}
 	// D210422
+	
+	private boolean isHTML5(Element target) {
+		DocumentTypeAdapter documentTypeAdapter = (DocumentTypeAdapter) ((INodeNotifier) target.getOwnerDocument()).getAdapterFor(DocumentTypeAdapter.class);
+		return (documentTypeAdapter != null && 
+				documentTypeAdapter.hasFeature(HTMLDocumentTypeConstants.HTML5));
+	}
+	
+	private boolean shouldValidateAttributeName(Element target, String attrName) {
+		if (!isHTML5(target)) return true;
+
+		Object adapter = (target instanceof IAdaptable ? ((IAdaptable)target).getAdapter(IResource.class) : null);
+		IProject project = (adapter instanceof IResource ? ((IResource)adapter).getProject() : null);
+		
+		Iterator excludedAttributes = getExcludedAttributeNames(project).iterator();
+		while (excludedAttributes.hasNext()) {
+			String excluded = (String)excludedAttributes.next();
+			StringMatcher strMatcher = (StringMatcher)fIgnorePatterns.get(excluded);
+			if (strMatcher == null) {
+				strMatcher = new StringMatcher(excluded);
+				fIgnorePatterns.put(excluded, strMatcher);
+			}
+			if (strMatcher.match(attrName))
+				return false;
+		}
+
+		return true;
+	}
+	
+	private Set getExcludedAttributeNames(IProject project) {
+		IScopeContext[] fLookupOrder = new IScopeContext[] {new InstanceScope(), new DefaultScope()};
+		if (project != null) {
+			ProjectScope projectScope = new ProjectScope(project);
+			if(projectScope.getNode(HTMLCorePlugin.getDefault().getBundle().getSymbolicName()).getBoolean(HTMLCorePreferenceNames.USE_PROJECT_SETTINGS, false))
+				fLookupOrder = new IScopeContext[] {projectScope, new InstanceScope(), new DefaultScope()};
+		}
+		
+		Set result = new HashSet();
+		if (fPreferenceService.getBoolean(HTMLCorePlugin.getDefault().getBundle().getSymbolicName(), 
+				HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES, HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES_DEFAULT, 
+				fLookupOrder)) {
+			String ignoreList = fPreferenceService.getString(HTMLCorePlugin.getDefault().getBundle().getSymbolicName(), 
+					HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE, HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT, 
+					fLookupOrder);
+			
+			if (ignoreList.trim().isEmpty())
+				return result;
+	
+			String[] names = ignoreList.split(","); //$NON-NLS-1$
+			for (int i = 0; names != null && i < names.length; i++) {
+				String name = names[i] == null ? null : names[i].trim();
+				if (name != null && !name.isEmpty()) 
+					result.add(name.toLowerCase());
+			}
+		}
+		return result; 
+	}
 }
diff --git a/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/validate/StringMatcher.java b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/validate/StringMatcher.java
new file mode 100644
index 0000000..6871163
--- /dev/null
+++ b/bundles/org.eclipse.wst.html.core/src/org/eclipse/wst/html/core/internal/validate/StringMatcher.java
@@ -0,0 +1,230 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2013 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
+ *******************************************************************************/
+package org.eclipse.wst.html.core.internal.validate;
+
+import java.util.ArrayList;
+
+/**
+ * A string pattern matcher, supporting "*" and "?" wild cards.
+ * 
+ * @since 3.2
+ */
+public class StringMatcher {
+	private static final char SINGLE_WILD_CARD = '\u0000';
+
+	/**
+	 * Boundary value beyond which we don't need to search in the text 
+	 */
+	private int bound = 0;
+
+	private boolean hasLeadingStar;
+
+	private boolean hasTrailingStar;
+
+	private final String pattern;
+
+	private final int patternLength;
+
+	/**
+	 * The pattern split into segments separated by *
+	 */
+	private String segments[];
+
+	/**
+	 * StringMatcher constructor takes in a String object that is a simple 
+	 * pattern which may contain '*' for 0 and many characters and
+	 * '?' for exactly one character.  
+	 *
+	 * Literal '*' and '?' characters must be escaped in the pattern 
+	 * e.g., "\*" means literal "*", etc.
+	 *
+	 * Escaping any other character (including the escape character itself), 
+	 * just results in that character in the pattern.
+	 * e.g., "\a" means "a" and "\\" means "\"
+	 *
+	 * If invoking the StringMatcher with string literals in Java, don't forget
+	 * escape characters are represented by "\\".
+	 *
+	 * @param pattern the pattern to match text against
+	 */
+	public StringMatcher(String pattern) {
+		if (pattern == null)
+			throw new IllegalArgumentException();
+		this.pattern = pattern;
+		patternLength = pattern.length();
+		parseWildCards();
+	}
+
+	/** 
+	 * @param text a simple regular expression that may only contain '?'(s)
+	 * @param start the starting index in the text for search, inclusive
+	 * @param end the stopping point of search, exclusive
+	 * @param p a simple regular expression that may contain '?'
+	 * @return the starting index in the text of the pattern , or -1 if not found 
+	 */
+	private int findPosition(String text, int start, int end, String p) {
+		boolean hasWildCard = p.indexOf(SINGLE_WILD_CARD) >= 0;
+		int plen = p.length();
+		for (int i = start, max = end - plen; i <= max; ++i) {
+			if (hasWildCard) {
+				if (regExpRegionMatches(text, i, p, 0, plen))
+					return i;
+			} else {
+				if (text.regionMatches(true, i, p, 0, plen))
+					return i;
+			}
+		}
+		return -1;
+	}
+
+	/**
+	 * Given the starting (inclusive) and the ending (exclusive) positions in the   
+	 * <code>text</code>, determine if the given substring matches with aPattern  
+	 * @return true if the specified portion of the text matches the pattern
+	 * @param text a String object that contains the substring to match 
+	 */
+	public boolean match(String text) {
+		if (text == null)
+			return false;
+		final int end = text.length();
+		final int segmentCount = segments.length;
+		if (segmentCount == 0 && (hasLeadingStar || hasTrailingStar)) // pattern contains only '*'(s)
+			return true;
+		if (end == 0)
+			return patternLength == 0;
+		if (patternLength == 0)
+			return false;
+		int currentTextPosition = 0;
+		if ((end - bound) < 0)
+			return false;
+		int segmentIndex = 0;
+		String current = segments[segmentIndex];
+
+		/* process first segment */
+		if (!hasLeadingStar) {
+			int currentLength = current.length();
+			if (!regExpRegionMatches(text, 0, current, 0, currentLength))
+				return false;
+			segmentIndex++;
+			currentTextPosition = currentTextPosition + currentLength;
+		}
+		if ((segmentCount == 1) && (!hasLeadingStar) && (!hasTrailingStar)) {
+			// only one segment to match, no wild cards specified
+			return currentTextPosition == end;
+		}
+		/* process middle segments */
+		while (segmentIndex < segmentCount) {
+			current = segments[segmentIndex];
+			int currentMatch = findPosition(text, currentTextPosition, end, current);
+			if (currentMatch < 0)
+				return false;
+			currentTextPosition = currentMatch + current.length();
+			segmentIndex++;
+		}
+
+		/* process final segment */
+		if (!hasTrailingStar && currentTextPosition != end) {
+			int currentLength = current.length();
+			return regExpRegionMatches(text, end - currentLength, current, 0, currentLength);
+		}
+		return segmentIndex == segmentCount;
+	}
+
+	/**
+	 * Parses the pattern into segments separated by wildcard '*' characters.
+	 */
+	private void parseWildCards() {
+		if (pattern.startsWith("*"))//$NON-NLS-1$
+			hasLeadingStar = true;
+		if (pattern.endsWith("*")) {//$NON-NLS-1$
+			/* make sure it's not an escaped wildcard */
+			if (patternLength > 1 && pattern.charAt(patternLength - 2) != '\\') {
+				hasTrailingStar = true;
+			}
+		}
+
+		ArrayList temp = new ArrayList();
+
+		int pos = 0;
+		StringBuffer buf = new StringBuffer();
+		while (pos < patternLength) {
+			char c = pattern.charAt(pos++);
+			switch (c) {
+				case '\\' :
+					if (pos >= patternLength) {
+						buf.append(c);
+					} else {
+						char next = pattern.charAt(pos++);
+						/* if it's an escape sequence */
+						if (next == '*' || next == '?' || next == '\\') {
+							buf.append(next);
+						} else {
+							/* not an escape sequence, just insert literally */
+							buf.append(c);
+							buf.append(next);
+						}
+					}
+					break;
+				case '*' :
+					if (buf.length() > 0) {
+						/* new segment */
+						temp.add(buf.toString());
+						bound += buf.length();
+						buf.setLength(0);
+					}
+					break;
+				case '?' :
+					/* append special character representing single match wildcard */
+					buf.append(SINGLE_WILD_CARD);
+					break;
+				default :
+					buf.append(c);
+			}
+		}
+
+		/* add last buffer to segment list */
+		if (buf.length() > 0) {
+			temp.add(buf.toString());
+			bound += buf.length();
+		}
+		segments = (String[])temp.toArray(new String[temp.size()]);
+	}
+
+	/**
+	 * 
+	 * @return boolean
+	 * @param text a String to match
+	 * @param tStart the starting index of match, inclusive
+	 * @param p a simple regular expression that may contain '?'
+	 * @param pStart The start position in the pattern
+	 * @param plen The length of the pattern
+	 */
+	private boolean regExpRegionMatches(String text, int tStart, String p, int pStart, int plen) {
+		while (plen-- > 0) {
+			char tchar = text.charAt(tStart++);
+			char pchar = p.charAt(pStart++);
+
+			// process wild cards, skipping single wild cards
+			if (pchar == SINGLE_WILD_CARD)
+				continue;
+			if (pchar == tchar)
+				continue;
+			if (Character.toUpperCase(tchar) == Character.toUpperCase(pchar))
+				continue;
+			// comparing after converting to upper case doesn't handle all cases;
+			// also compare after converting to lower case
+			if (Character.toLowerCase(tchar) == Character.toLowerCase(pchar))
+				continue;
+			return false;
+		}
+		return true;
+	}
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.wst.html.ui/icons/full/etool16/donotvalidate.png b/bundles/org.eclipse.wst.html.ui/icons/full/etool16/donotvalidate.png
new file mode 100644
index 0000000..ecb20f5
--- /dev/null
+++ b/bundles/org.eclipse.wst.html.ui/icons/full/etool16/donotvalidate.png
Binary files differ
diff --git a/bundles/org.eclipse.wst.html.ui/plugin.xml b/bundles/org.eclipse.wst.html.ui/plugin.xml
index 1461078..8bf535d 100644
--- a/bundles/org.eclipse.wst.html.ui/plugin.xml
+++ b/bundles/org.eclipse.wst.html.ui/plugin.xml
@@ -50,6 +50,10 @@
 			class="org.eclipse.wst.xml.ui.internal.correction.XMLQuickAssistProcessor"
 			target="org.eclipse.wst.html.HTML_DEFAULT" />
 		<provisionalConfiguration
+			type="org.eclipse.jface.text.quickassist.IQuickAssistProcessor"
+			class="org.eclipse.wst.html.ui.internal.text.correction.HTMLAttributeValidationQuickFixProcessor"
+			target="org.eclipse.wst.html.HTML_DEFAULT" />
+		<provisionalConfiguration
 			type="autoeditstrategy"
 			class="org.eclipse.wst.html.ui.internal.autoedit.StructuredAutoEditStrategyHTML"
 			target="org.eclipse.wst.html.HTML_DEFAULT, org.eclipse.wst.html.HTML_DECLARATION" />
diff --git a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/HTMLUIMessages.java b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/HTMLUIMessages.java
index e1f4b83..1538506 100644
--- a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/HTMLUIMessages.java
+++ b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/HTMLUIMessages.java
@@ -1,5 +1,5 @@
 /**********************************************************************
- * Copyright (c) 2005, 2011 IBM Corporation and others. All rights reserved.   This
+ * Copyright (c) 2005, 2013 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
@@ -209,4 +209,13 @@
 	// Hyperlinks
 	public static String Hyperlink_line;
 	public static String Open;
+	
+	// Ignore Attribute Name Patterns
+	public static String IgnoreAttributeNames;
+	public static String IgnoreAttributeNamesPattern;
+	public static String BadIgnoreAttributeNamesPattern;
+	public static String DoNotValidateAttribute;
+	public static String DoNotValidateAllAttributes;
+	public static String DoNotValidateAttributeAddInfo;
+	public static String DoNotValidateAllAttributesAddInfo;
 }
diff --git a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/HTMLUIPluginResources.properties b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/HTMLUIPluginResources.properties
index abf07c6..b1e3b78 100644
--- a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/HTMLUIPluginResources.properties
+++ b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/HTMLUIPluginResources.properties
@@ -1,5 +1,5 @@
 ###############################################################################
-# Copyright (c) 2004, 2012 IBM Corporation and others.
+# Copyright (c) 2004, 2013 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
@@ -181,3 +181,11 @@
 
 Hyperlink_line={0}={1} : line {2}
 Open=Open ''{0}''
+
+IgnoreAttributeNames=Ignore specified attribute names in validation
+IgnoreAttributeNamesPattern=Specify comma-separated patterns for attribute names to be ignored:
+BadIgnoreAttributeNamesPattern=''{0}'' is not a valid attribute names pattern
+DoNotValidateAttribute=Ignore ''{0}'' attribute in validation
+DoNotValidateAllAttributes=Ignore all ''{0}'' attributes in validation
+DoNotValidateAttributeAddInfo=Adds the name of attribute to the list of ignored attributes for HTML Attribute Validator<br>You may edit the list of ignored attributes at <b>Web&#x2192;HTML Files&#x2192;Validation</b> Preference Page
+DoNotValidateAllAttributesAddInfo=Adds the pattern based on the attribute name to the list of ignored attributes for HTML Attribute Validator<br>You may edit the list of ignored attributes at <b>Web&#x2192;HTML Files&#x2192;Validation</b> Preference Page
\ No newline at end of file
diff --git a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/editor/HTMLEditorPluginImages.java b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/editor/HTMLEditorPluginImages.java
index 1fa0f24..be2c050 100644
--- a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/editor/HTMLEditorPluginImages.java
+++ b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/editor/HTMLEditorPluginImages.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2004, 2006 IBM Corporation and others.
+ * Copyright (c) 2004, 2013 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
@@ -29,4 +29,5 @@
 	public static final String IMG_OBJ_TAG_TITLE = "icons/full/obj16/tag-title.gif";			//$NON-NLS-1$
 	public static final String IMG_OBJ_TAG = "icons/full/obj16/tag.gif";			//$NON-NLS-1$
 	public static final String IMG_WIZBAN_NEWHTMLFILE = "icons/full/wizban/newhfile_wiz.png";			//$NON-NLS-1$
-}
+	public static final String IMG_DTOOL_DO_NOT_VALIDATE = "icons/full/etool16/donotvalidate.png";			//$NON-NLS-1$
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/preferences/ui/HTMLValidationPreferencePage.java b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/preferences/ui/HTMLValidationPreferencePage.java
index e3db43c..3e14f55 100644
--- a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/preferences/ui/HTMLValidationPreferencePage.java
+++ b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/preferences/ui/HTMLValidationPreferencePage.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2008, 2012 IBM Corporation and others.
+ * Copyright (c) 2008, 2013 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
@@ -10,35 +10,152 @@
  *******************************************************************************/
 package org.eclipse.wst.html.ui.internal.preferences.ui;
 
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.DefaultScope;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.core.runtime.preferences.IPreferencesService;
+import org.eclipse.core.runtime.preferences.IScopeContext;
 import org.eclipse.jface.dialogs.IDialogSettings;
 import org.eclipse.jface.layout.PixelConverter;
+import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.Widget;
 import org.eclipse.ui.IWorkbench;
 import org.eclipse.ui.forms.widgets.ExpandableComposite;
 import org.eclipse.wst.html.core.internal.HTMLCorePlugin;
 import org.eclipse.wst.html.core.internal.preferences.HTMLCorePreferenceNames;
 import org.eclipse.wst.html.ui.internal.HTMLUIMessages;
 import org.eclipse.wst.html.ui.internal.HTMLUIPlugin;
+import org.eclipse.wst.html.ui.internal.Logger;
 import org.eclipse.wst.sse.core.internal.validate.ValidationMessage;
 import org.eclipse.wst.sse.ui.internal.preferences.ui.AbstractValidationSettingsPage;
 import org.eclipse.wst.sse.ui.internal.preferences.ui.ScrolledPageContent;
+import org.osgi.service.prefs.BackingStoreException;
 
 public class HTMLValidationPreferencePage extends AbstractValidationSettingsPage {
 	
+	public static final String PROPERTY_PAGE_ID = "org.eclipse.wst.html.ui.propertyPage.project.validation";
+
+	public static final String PREFERENCE_PAGE_ID = "org.eclipse.wst.html.ui.preferences.validation";
+
 	private static final int[] SEVERITIES = {ValidationMessage.ERROR, ValidationMessage.WARNING, ValidationMessage.IGNORE};
 	
 	private static final String SETTINGS_SECTION_NAME = "HTMLValidationSeverities";//$NON-NLS-1$
+
+	private class BooleanData {
+		private String fKey;
+		private boolean fValue;
+		boolean originalValue = false; 
+		
+		public BooleanData(String key) {
+			fKey = key;
+		}
+		
+		public String getKey() {
+			return fKey;
+		}
+		
+		/**
+		 * Sets enablement for the attribute names ignorance
+		 * 
+		 * @param severity the severity level
+		 */
+		public void setValue(boolean value) {
+			fValue = value;
+		}
+		
+		/**
+		 * Returns the value for the attribute names ignorance
+		 * 
+		 * @return
+		 */
+		public boolean getValue() {
+			return fValue;
+		}
+		
+		boolean isChanged() {
+			return (originalValue != fValue);
+		}
+	}
+	
+	private class TextData {
+		private String fKey;
+		private String fValue;
+		String originalValue = ""; //$NON-NLS-1$
+		
+		public TextData(String key) {
+			fKey = key;
+		}
+		
+		public String getKey() {
+			return fKey;
+		}
+		
+		/**
+		 * Sets the ignored attribute names pattern
+		 * 
+		 * @param severity the severity level
+		 */
+		public void setValue(String value) {
+			fValue = value;
+		}
+		
+		/**
+		 * Returns non-null value for the ignored attribute names pattern
+		 * 
+		 * @return
+		 */
+		public String getValue() {
+			return fValue != null ? fValue : ""; //$NON-NLS-1$
+		}
+		
+		boolean isChanged() {
+			return !originalValue.equalsIgnoreCase(fValue);
+		}
+	}
 	
 	public HTMLValidationPreferencePage() {
 		super();
+		fPreferencesService = Platform.getPreferencesService();
 	}
 	
 	private PixelConverter fPixelConverter;
+	private Button fIgnoreAttributeNames;
+	private Label fIgnoredAttributeNamesLabel;
+	private Text fIgnoredAttributeNames;
+	private IPreferencesService fPreferencesService = null;
+	
+	private boolean fUseOriginOverrides = false;
+	private boolean fIgnoreAttributeNamesOriginOverride = HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES_DEFAULT;
+	private String  fIgnoredAttributeNamesOriginOverride = HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT;
+
+	public void overrideOriginValues(boolean enableIgnore, String attributeNames) {
+		fIgnoreAttributeNamesOriginOverride = enableIgnore;
+		fIgnoredAttributeNamesOriginOverride = attributeNames;
+		fUseOriginOverrides = true;
+		
+		if (fIgnoreAttributeNames != null) {
+			BooleanData data = (BooleanData)fIgnoreAttributeNames.getData();
+			if (data != null)
+				data.originalValue = fIgnoreAttributeNamesOriginOverride;
+		}
+		if (fIgnoredAttributeNames != null) {
+			TextData data = (TextData)fIgnoredAttributeNames.getData();
+			if (data != null)
+				data.originalValue = fIgnoredAttributeNamesOriginOverride;
+		}
+	}
 	
 	protected Control createCommonContents(Composite parent) {
 		final Composite page = new Composite(parent, SWT.NULL);
@@ -71,6 +188,59 @@
 		layout.marginWidth= 0;
 		composite.setLayout(layout);
 		
+		// Ignored Attribute Names Pattern
+		BooleanData ignoreData = new BooleanData(HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES);
+		fIgnoreAttributeNames = new Button(composite, SWT.CHECK);
+		fIgnoreAttributeNames.setData(ignoreData);
+		fIgnoreAttributeNames.setFont(page.getFont());
+		fIgnoreAttributeNames.setText(HTMLUIMessages.IgnoreAttributeNames);
+		fIgnoreAttributeNames.setEnabled(true);
+		
+		boolean ignoreAttributeNamesIsSelected = fPreferencesService.getBoolean(getPreferenceNodeQualifier(), 
+				ignoreData.getKey(), HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES_DEFAULT, createPreferenceScopes());
+		ignoreData.setValue(ignoreAttributeNamesIsSelected);
+		ignoreData.originalValue = fUseOriginOverrides ? fIgnoreAttributeNamesOriginOverride : ignoreAttributeNamesIsSelected;
+		
+		fIgnoreAttributeNames.setSelection(ignoreData.getValue());
+		fIgnoreAttributeNames.addSelectionListener(new SelectionListener() {
+			public void widgetDefaultSelected(SelectionEvent e) {
+				controlChanged(e.widget);
+			}
+			public void widgetSelected(SelectionEvent e) {
+				controlChanged(e.widget);
+			}
+		});
+		fIgnoreAttributeNames.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 3, 1));
+		
+		fIgnoredAttributeNamesLabel = new Label(composite, SWT.LEFT | SWT.WRAP);
+		fIgnoredAttributeNamesLabel.setFont(composite.getFont());
+		fIgnoredAttributeNamesLabel.setEnabled(ignoreData.getValue());
+		fIgnoredAttributeNamesLabel.setText(HTMLUIMessages.IgnoreAttributeNamesPattern);
+		fIgnoredAttributeNamesLabel.setLayoutData(new GridData(SWT.FILL, SWT.END, true, false, 3, 1));
+		setHorizontalIndent(fIgnoredAttributeNamesLabel, 20);
+
+		TextData data = new TextData(HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE);
+		fIgnoredAttributeNames = new Text(composite, SWT.SINGLE | SWT.BORDER);
+		fIgnoredAttributeNames.setData(data);
+		fIgnoredAttributeNames.setTextLimit(500);
+		fIgnoredAttributeNames.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1));
+		setHorizontalIndent(fIgnoredAttributeNames, 20);
+		setWidthHint(fIgnoredAttributeNames, convertWidthInCharsToPixels(65));
+		String ignoredAttributeNames = fPreferencesService.getString(getPreferenceNodeQualifier(), data.getKey(), HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT, createPreferenceScopes());
+		data.setValue(ignoredAttributeNames);
+		data.originalValue = fUseOriginOverrides ? fIgnoredAttributeNamesOriginOverride : ignoredAttributeNames;
+		fIgnoredAttributeNames.setText(data.getValue());
+		
+		fIgnoredAttributeNames.addModifyListener(new ModifyListener() {
+			
+			public void modifyText(ModifyEvent e) {
+				if (verifyIgnoredAttributeNames()) {
+					controlChanged(e.widget);
+				}
+			}
+		});
+		controlChanged(fIgnoreAttributeNames);
+
 		Label description = new Label(composite, SWT.NONE);
 		description.setText(HTMLUIMessages.Validation_description);
 		description.setFont(page.getFont());
@@ -285,16 +455,129 @@
 		
 		return spContent;
 	}
+
+	private void setHorizontalIndent(Control control, int indent) {
+		Object ld= control.getLayoutData();
+		if (ld instanceof GridData) {
+			((GridData) ld).horizontalIndent= indent;
+		}
+	}
+	
+	private void setWidthHint(Control control, int widthHint) {
+		Object ld= control.getLayoutData();
+		if (ld instanceof GridData) {
+			((GridData)ld).widthHint= widthHint;
+		}
+	}
+	
+	private boolean verifyIgnoredAttributeNames() {
+		final String text = fIgnoredAttributeNames.getText().trim();
+		if (text.length() == 0)
+			return true;
+
+		boolean valid = true;
+		for (int i = 0; valid && i < text.length(); i++) {
+			if (!Character.isJavaIdentifierPart(text.charAt(i)) &&
+					'-' != text.charAt(i) && '_' != text.charAt(i) &&
+					'*' != text.charAt(i) && '?' != text.charAt(i) &&
+					',' != text.charAt(i))
+				valid = false;
+		}
+		
+		if (!valid) {
+			setErrorMessage(NLS.bind(HTMLUIMessages.BadIgnoreAttributeNamesPattern, text));
+			setValid(false);
+		}
+		else {
+			setErrorMessage(null);
+			setValid(true);
+		}
+		return valid;
+	}
+	
+	protected void controlChanged(Widget widget) {
+		if (widget instanceof Text) {
+			TextData data= (TextData) widget.getData();
+			data.setValue(((Text)widget).getText());
+		} else if (widget instanceof Button) {
+			BooleanData data = (BooleanData) widget.getData();
+			if (data != null) {
+				data.setValue(((Button)widget).getSelection());
+				fIgnoredAttributeNamesLabel.setEnabled(data.getValue());
+				fIgnoredAttributeNames.setEnabled(data.getValue());
+				if (data.getValue()) {
+					fIgnoredAttributeNames.setFocus();
+				}
+			}
+		}
+	}
+
+	/**
+	 * Returns true in case of the Attribute Names to ignore preferences is changed
+	 * causing the full validation to be requested.
+	 */
+	protected boolean shouldRevalidateOnSettingsChange() {
+		TextData data = (TextData)fIgnoredAttributeNames.getData();
+		if (data.isChanged())
+			return true;
+		
+		BooleanData ignoreData = (BooleanData)fIgnoreAttributeNames.getData();
+		if (ignoreData.isChanged())
+			return true;
+		
+		return super.shouldRevalidateOnSettingsChange();
+	}
+	
+	/*
+	 * (non-Javadoc)
+	 * @see org.eclipse.wst.sse.ui.internal.preferences.ui.AbstractSettingsPage#storeValues()
+	 */
+	protected void storeValues() {
+		IScopeContext[] contexts = createPreferenceScopes();
+
+		BooleanData ignoreData = (BooleanData)fIgnoreAttributeNames.getData();
+		contexts[0].getNode(getPreferenceNodeQualifier()).putBoolean(ignoreData.getKey(), ignoreData.getValue()); 
+
+		TextData data = (TextData)fIgnoredAttributeNames.getData();
+		contexts[0].getNode(getPreferenceNodeQualifier()).put(data.getKey(), data.getValue()); 
+		
+		
+		for(int i = 0; i < contexts.length; i++) {
+			try {
+				contexts[i].getNode(getPreferenceNodeQualifier()).flush();
+			} catch (BackingStoreException e) {
+				Logger.logException(e);
+			}
+		}
+		super.storeValues();
+	}
 	
 	/*
 	 * (non-Javadoc)
 	 * @see org.eclipse.jface.preference.PreferencePage#performDefaults()
 	 */
 	protected void performDefaults() {
+		resetIgnoreAttributeNamesPattern();
 		resetSeverities();
 		super.performDefaults();
 	}
 	
+	protected void resetIgnoreAttributeNamesPattern() {
+		IEclipsePreferences defaultContext = new DefaultScope().getNode(getPreferenceNodeQualifier());
+		BooleanData ignoreData = (BooleanData)fIgnoreAttributeNames.getData();
+		boolean ignoreAttributeNames = defaultContext.getBoolean(ignoreData.getKey(), HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES_DEFAULT);
+		ignoreData.setValue(ignoreAttributeNames);
+		fIgnoreAttributeNames.setSelection(ignoreData.getValue());
+
+		TextData data = (TextData)fIgnoredAttributeNames.getData();
+		String ignoredAttributeNames = defaultContext.get(data.getKey(), HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT);
+		data.setValue(ignoredAttributeNames);
+		fIgnoredAttributeNames.setText(data.getValue());
+		
+		controlChanged(fIgnoreAttributeNames);
+	}
+	
+	
 	protected IDialogSettings getDialogSettings() {
 		return HTMLUIPlugin.getDefault().getDialogSettings();
 	}
@@ -317,7 +600,7 @@
 	}
 
 	protected String getPreferencePageID() {
-		return "org.eclipse.wst.html.ui.preferences.validation";//$NON-NLS-1$
+		return PREFERENCE_PAGE_ID;
 	}
 
 	protected String getProjectSettingsKey() {
@@ -325,7 +608,7 @@
 	}
 
 	protected String getPropertyPageID() {
-		return "org.eclipse.wst.html.ui.propertyPage.project.validation";//$NON-NLS-1$
+		return PROPERTY_PAGE_ID;
 	}
 
 	public void init(IWorkbench workbench) {
diff --git a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/text/correction/HTMLAttributeValidationQuickFixProcessor.java b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/text/correction/HTMLAttributeValidationQuickFixProcessor.java
new file mode 100644
index 0000000..5e3f7cd
--- /dev/null
+++ b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/text/correction/HTMLAttributeValidationQuickFixProcessor.java
@@ -0,0 +1,228 @@
+/*******************************************************************************
+ * Copyright (c) 2013 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
+ *******************************************************************************/
+package org.eclipse.wst.html.ui.internal.text.correction;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ProjectScope;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.DefaultScope;
+import org.eclipse.core.runtime.preferences.IPreferencesService;
+import org.eclipse.core.runtime.preferences.IScopeContext;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
+import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.IAnnotationModel;
+import org.eclipse.jface.text.source.IAnnotationModelExtension2;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.ui.texteditor.MarkerAnnotation;
+import org.eclipse.wst.html.core.internal.HTMLCoreMessages;
+import org.eclipse.wst.html.core.internal.HTMLCorePlugin;
+import org.eclipse.wst.html.core.internal.preferences.HTMLCorePreferenceNames;
+import org.eclipse.wst.html.core.internal.validate.StringMatcher;
+import org.eclipse.wst.html.ui.internal.HTMLUIMessages;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.ui.internal.contentassist.ContentAssistUtils;
+import org.eclipse.wst.sse.ui.internal.reconcile.TemporaryAnnotation;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
+import org.w3c.dom.NamedNodeMap;
+
+public class HTMLAttributeValidationQuickFixProcessor implements IQuickAssistProcessor {
+
+	private StringMatcher UNDEFINED_ATTRIBUTE_NAME_MATCHER;
+	private IPreferencesService fPreferenceService;
+
+	public HTMLAttributeValidationQuickFixProcessor() {
+		String templ = HTMLCoreMessages.Undefined_attribute_name___ERROR_;
+		templ = templ.replaceAll("\\{[0-9]*\\}", "\\*"); //$NON-NLS-1$
+		UNDEFINED_ATTRIBUTE_NAME_MATCHER = new StringMatcher(templ);
+		fPreferenceService = Platform.getPreferencesService();
+	}
+	
+	/*
+	 * @see org.eclipse.jface.text.quickassist.IQuickAssistProcessor#getErrorMessage()
+	 */
+	public String getErrorMessage() {
+		return null;
+	}
+
+	/*
+	 * @see org.eclipse.jface.text.quickassist.IQuickAssistProcessor#canFix(org.eclipse.jface.text.source.Annotation)
+	 */
+	public boolean canFix(Annotation annotation) {
+		boolean result = false;
+
+		String text = null;
+		if (annotation instanceof TemporaryAnnotation) {
+			TemporaryAnnotation tempAnnotation = (TemporaryAnnotation) annotation;
+			int problemID = tempAnnotation.getProblemID();
+			text = tempAnnotation.getText();
+
+			if (problemID == 0 && text != null)
+				result = true;
+		} else if (annotation instanceof MarkerAnnotation) {
+			MarkerAnnotation markerAnnotation = (MarkerAnnotation) annotation;
+			text = markerAnnotation.getText();
+			IMarker marker = markerAnnotation.getMarker();
+			IResource resource = marker == null ? null : marker.getResource();
+			if (resource != null && resource.exists() && resource.isAccessible() && text != null) {
+				result = true;
+			}
+		}
+		
+		result = (result && UNDEFINED_ATTRIBUTE_NAME_MATCHER.match(text));
+
+		return result;
+	}
+
+	/*
+	 * @see org.eclipse.jface.text.quickassist.IQuickAssistProcessor#canAssist(org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext)
+	 */
+	public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
+		return true;
+	}
+
+	/*
+	 * @see org.eclipse.jface.text.quickassist.IQuickAssistProcessor#computeQuickAssistProposals(org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext)
+	 */
+	public ICompletionProposal[] computeQuickAssistProposals(
+			IQuickAssistInvocationContext invocationContext) {
+		ISourceViewer viewer = invocationContext.getSourceViewer();
+		int documentOffset = invocationContext.getOffset();
+		int length = viewer != null ? viewer.getSelectedRange().y : 0;
+
+		IAnnotationModel model = viewer.getAnnotationModel();
+		if (model == null)
+			return null;
+
+		List proposals = new ArrayList();
+		if (model instanceof IAnnotationModelExtension2) {
+			Iterator iter = ((IAnnotationModelExtension2) model).getAnnotationIterator(documentOffset, length, true, true);
+			while (iter.hasNext()) {
+				Annotation anno = (Annotation) iter.next();
+				if (canFix(anno)) {
+					int offset = -1;
+					
+					if (anno instanceof TemporaryAnnotation) {
+						offset = ((TemporaryAnnotation)anno).getPosition().getOffset();
+					} else if (anno instanceof MarkerAnnotation) {
+						offset = ((MarkerAnnotation)anno).getMarker().getAttribute(IMarker.CHAR_START, -1);
+					}
+					if (offset == -1)
+						continue;
+
+					IDOMNode node = (IDOMNode) ContentAssistUtils.getNodeAt(viewer, offset);
+
+					Object adapter = (node instanceof IAdaptable ? ((IAdaptable)node).getAdapter(IResource.class) : null);
+					IProject project = (adapter instanceof IResource ? ((IResource)adapter).getProject() : null);
+
+					IScopeContext[] fLookupOrder = new IScopeContext[] {new InstanceScope(), new DefaultScope()};
+					if (project != null) {
+						ProjectScope projectScope = new ProjectScope(project);
+						if(projectScope.getNode(getPreferenceNodeQualifier())
+								.getBoolean(getProjectSettingsKey(), false))
+							fLookupOrder = new IScopeContext[] {projectScope, new InstanceScope(), new DefaultScope()};
+					}
+					
+					String ignoreList = fPreferenceService.getString(
+							getPreferenceNodeQualifier(), HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE, 
+							HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT, fLookupOrder);
+
+					Set result = new HashSet();
+					if (!ignoreList.trim().isEmpty()) {
+						String[] names = ignoreList.split(","); //$NON-NLS-1$
+						for (int i = 0; names != null && i < names.length; i++) {
+							String name = names[i] == null ? null : names[i].trim();
+							if (name != null && !name.isEmpty()) 
+								result.add(name.toLowerCase());
+						}
+					}
+					
+					String name = getAttributeName(node, offset);
+					if (name == null) continue;
+					
+					if (!result.contains(name.toLowerCase())) {
+						IgnoreAttributeNameCompletionProposal p = new IgnoreAttributeNameCompletionProposal(
+								name, offset, NLS.bind(HTMLUIMessages.DoNotValidateAttribute, name), 
+								HTMLUIMessages.DoNotValidateAttributeAddInfo, node);
+						if (!proposals.contains(p))
+							proposals.add(p);  
+					}
+					
+					int dashIndex = name.indexOf('-');
+					while (dashIndex != -1) {
+						StringBuffer namePattern = new StringBuffer(name.substring(0, dashIndex + 1)).append('*');
+						
+						// Do not continue creating proposals for the rest of patterns if 
+						// a more common pattern is already created
+						if (result.contains(namePattern.toString().toLowerCase())) 
+							break;
+						
+						if (!result.contains(namePattern.toString().toLowerCase())) {
+							IgnoreAttributeNameCompletionProposal p = new IgnoreAttributeNameCompletionProposal(
+									namePattern.toString(), offset, NLS.bind(HTMLUIMessages.DoNotValidateAllAttributes, namePattern.toString()), 
+									HTMLUIMessages.DoNotValidateAllAttributesAddInfo, node); 
+							if (!proposals.contains(p))
+								proposals.add(p);  
+						}
+						dashIndex = name.indexOf('-', dashIndex + 1);
+					}
+				}
+			}
+		}
+
+		if (proposals.isEmpty())
+			return null;
+
+		return (ICompletionProposal[]) proposals.toArray(new ICompletionProposal[proposals.size()]);
+
+	}
+
+	private String getAttributeName(IDOMNode node, int offset) {
+		IStructuredDocumentRegion startStructuredDocumentRegion = node == null ? null : node.getStartStructuredDocumentRegion();
+
+		// Only a tag start can have attributes
+		if ((startStructuredDocumentRegion != null) && startStructuredDocumentRegion.containsOffset(offset)) {
+			NamedNodeMap attributes = node.getAttributes();
+			if (attributes == null) return null;
+			
+			for (int i = 0; i < attributes.getLength(); i++) {
+				IDOMAttr attr = (IDOMAttr)attributes.item(i);
+				
+				if (attr.getNameRegionStartOffset() <= offset && attr.getNameRegionEndOffset() >= offset) {
+					return attr.getName();
+				}
+			}
+		}
+		
+		return null;
+	}
+	
+	private String getPreferenceNodeQualifier() {
+		return HTMLCorePlugin.getDefault().getBundle().getSymbolicName();
+	}
+
+	private String getProjectSettingsKey() {
+		return HTMLCorePreferenceNames.USE_PROJECT_SETTINGS;
+	}
+}
diff --git a/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/text/correction/IgnoreAttributeNameCompletionProposal.java b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/text/correction/IgnoreAttributeNameCompletionProposal.java
new file mode 100644
index 0000000..14c9f80
--- /dev/null
+++ b/bundles/org.eclipse.wst.html.ui/src/org/eclipse/wst/html/ui/internal/text/correction/IgnoreAttributeNameCompletionProposal.java
@@ -0,0 +1,187 @@
+/*******************************************************************************
+ * Copyright (c) 2013 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
+ *******************************************************************************/
+package org.eclipse.wst.html.ui.internal.text.correction;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ProjectScope;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.DefaultScope;
+import org.eclipse.core.runtime.preferences.IPreferencesService;
+import org.eclipse.core.runtime.preferences.IScopeContext;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.jface.preference.PreferenceDialog;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.dialogs.PreferencesUtil;
+import org.eclipse.wst.html.core.internal.HTMLCorePlugin;
+import org.eclipse.wst.html.core.internal.preferences.HTMLCorePreferenceNames;
+import org.eclipse.wst.html.ui.internal.HTMLUIPlugin;
+import org.eclipse.wst.html.ui.internal.Logger;
+import org.eclipse.wst.html.ui.internal.editor.HTMLEditorPluginImageHelper;
+import org.eclipse.wst.html.ui.internal.editor.HTMLEditorPluginImages;
+import org.eclipse.wst.html.ui.internal.preferences.ui.HTMLValidationPreferencePage;
+import org.osgi.service.prefs.BackingStoreException;
+import org.w3c.dom.Node;
+
+public class IgnoreAttributeNameCompletionProposal implements ICompletionProposal {
+	/** The string to be added to the Ignored HTML Attributes list. */
+	private String fPattern;
+	/** The target node */
+	private Node fTarget;
+	/** The string to be displayed in the completion proposal popup. */
+	private String fDisplayString;
+	/** The replacement offset. */
+	private int fReplacementOffset;
+	/** The context information of this proposal. */
+	private IContextInformation fContextInformation;
+	/** The additional info of this proposal. */
+	private String fAdditionalProposalInfo;
+	
+	private IPreferencesService fPreferenceService;
+
+	public IgnoreAttributeNameCompletionProposal(String pattern, int offset, String displayString, String additionalProposalInfo, Node target) {
+		fReplacementOffset= offset;
+		fPattern = pattern;
+		fDisplayString= displayString;
+		fAdditionalProposalInfo= additionalProposalInfo;
+		fTarget = target;
+		fPreferenceService = Platform.getPreferencesService();
+	}
+
+	/*
+	 * @see ICompletionProposal#apply(IDocument)
+	 */
+	public void apply(IDocument document) {
+		Object adapter = (fTarget instanceof IAdaptable ? ((IAdaptable)fTarget).getAdapter(IResource.class) : null);
+		IProject project = (adapter instanceof IResource ? ((IResource)adapter).getProject() : null);
+
+		IScopeContext[] fLookupOrder = new IScopeContext[] {new InstanceScope(), new DefaultScope()};
+		boolean hasProjectSettings = false;
+		if (project != null) {
+			ProjectScope projectScope = new ProjectScope(project);
+			if(projectScope.getNode(getPreferenceNodeQualifier())
+					.getBoolean(getProjectSettingsKey(), false)) {
+				hasProjectSettings = true;
+				fLookupOrder = new IScopeContext[] {projectScope, new InstanceScope(), new DefaultScope()};
+			}
+		}
+		
+		boolean originalEnableIgnore = fPreferenceService.getBoolean(
+				getPreferenceNodeQualifier(), HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES, 
+				HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES_DEFAULT, fLookupOrder);
+		
+		String originalAttributeNames = fPreferenceService.getString(
+				getPreferenceNodeQualifier(), HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE, 
+				HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT, fLookupOrder);
+		
+		StringBuffer ignoreList = new StringBuffer(originalAttributeNames);
+	
+		if (ignoreList.length() > 0)
+			ignoreList.append(',');
+		
+		ignoreList.append(fPattern);
+
+		fLookupOrder[0].getNode(getPreferenceNodeQualifier())
+			.putBoolean(HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES, true); 
+
+		fLookupOrder[0].getNode(getPreferenceNodeQualifier())
+			.put(HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE, ignoreList.toString()); 
+
+		for(int i = 0; i < fLookupOrder.length; i++) {
+			try {
+				fLookupOrder[i].getNode(getPreferenceNodeQualifier()).flush();
+			} catch (BackingStoreException e) {
+				Logger.logException(e);
+			}
+		}
+
+		PreferenceDialog dialog = hasProjectSettings ? 
+				PreferencesUtil.createPropertyDialogOn(getShell(), project, HTMLValidationPreferencePage.PROPERTY_PAGE_ID, null, null) :
+					PreferencesUtil.createPreferenceDialogOn(getShell(), HTMLValidationPreferencePage.PREFERENCE_PAGE_ID, null, null);
+		if (dialog != null) {
+			Object page = dialog.getSelectedPage();
+			if (page instanceof HTMLValidationPreferencePage) {
+				((HTMLValidationPreferencePage)page).overrideOriginValues(originalEnableIgnore, originalAttributeNames);
+			}
+			dialog.open();
+		}
+	}
+	
+	/*
+	 * @see ICompletionProposal#getDisplayString()
+	 */
+	public String getDisplayString() {
+		if (fDisplayString != null)
+			return fDisplayString;
+		return ""; //$NON-NLS-1$
+	}
+
+	/*
+	 * @see ICompletionProposal#getAdditionalProposalInfo()
+	 */
+	public String getAdditionalProposalInfo() {
+		return fAdditionalProposalInfo;
+	}
+
+	/*
+	 * @see ICompletionProposal#getImage()
+	 */
+	public Image getImage() {
+		return HTMLEditorPluginImageHelper.getInstance().getImage(HTMLEditorPluginImages.IMG_DTOOL_DO_NOT_VALIDATE);
+	}
+
+	/*
+	 * @see ICompletionProposal#getSelection(IDocument)
+	 */
+	public Point getSelection(IDocument document) {
+		return new Point(fReplacementOffset /*+ fCursorPosition*/, 0);
+	}
+
+	/*
+	 * @see ICompletionProposal#getContextInformation()
+	 */
+	public IContextInformation getContextInformation() {
+		return fContextInformation;
+	}
+
+	/*
+	 * @see java.lang.Object#equals(java.lang.Object)
+	 */
+	public boolean equals(Object obj) {
+		if (obj instanceof IgnoreAttributeNameCompletionProposal) {
+			IgnoreAttributeNameCompletionProposal p = (IgnoreAttributeNameCompletionProposal)obj;
+			return (this.fPattern.equals(p.fPattern) && this.fTarget == p.fTarget && this.fReplacementOffset == p.fReplacementOffset);
+		}
+		return false;
+	}
+	
+	private String getPreferenceNodeQualifier() {
+		return HTMLCorePlugin.getDefault().getBundle().getSymbolicName();
+	}
+
+	private String getProjectSettingsKey() {
+		return HTMLCorePreferenceNames.USE_PROJECT_SETTINGS;
+	}
+	
+	private Shell getShell() {
+		IWorkbench workBench = HTMLUIPlugin.getDefault().getWorkbench();
+		IWorkbenchWindow workBenchWindow = workBench == null ? null : workBench.getActiveWorkbenchWindow();
+		return workBenchWindow == null ? null : workBenchWindow.getShell();
+	}
+}