| /******************************************************************************* |
| * Copyright (c) 2004 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.validate; |
| |
| |
| |
| import org.eclipse.wst.common.contentmodel.CMAttributeDeclaration; |
| import org.eclipse.wst.common.contentmodel.CMDataType; |
| import org.eclipse.wst.common.contentmodel.CMElementDeclaration; |
| import org.eclipse.wst.common.contentmodel.CMNamedNodeMap; |
| import org.eclipse.wst.sse.core.IndexedRegion; |
| import org.eclipse.wst.sse.core.text.ITextRegion; |
| import org.eclipse.wst.xml.core.document.XMLAttr; |
| import org.eclipse.wst.xml.core.document.XMLElement; |
| import org.eclipse.wst.xml.core.document.XMLNode; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NamedNodeMap; |
| |
| public class HTMLAttributeValidator extends PrimeValidator { |
| |
| private static final int REGION_NAME = 1; |
| private static final int REGION_VALUE = 2; |
| //<<D210422 |
| private static final char SINGLE_QUOTE = '\''; |
| private static final char DOUBLE_QUOTE = '\"'; |
| |
| //D210422 |
| /** |
| * HTMLAttributeValidator constructor comment. |
| */ |
| public HTMLAttributeValidator() { |
| super(); |
| } |
| |
| /** |
| */ |
| private Segment getErrorSegment(XMLNode errorNode, int regionType) { |
| ITextRegion rgn = null; |
| switch (regionType) { |
| case REGION_NAME : |
| rgn = errorNode.getNameRegion(); |
| break; |
| case REGION_VALUE : |
| rgn = errorNode.getValueRegion(); |
| break; |
| default : |
| // nothing to do. |
| break; |
| } |
| if (rgn != null) { |
| if (errorNode instanceof XMLAttr) { |
| XMLElement ownerElement = (XMLElement) ((XMLAttr) errorNode).getOwnerElement(); |
| if (ownerElement != null) { |
| int regionStartOffset = ownerElement.getFirstStructuredDocumentRegion().getStartOffset(rgn); |
| int regionLength = rgn.getLength(); |
| return new Segment(regionStartOffset, regionLength); |
| } |
| } |
| } |
| return new Segment(errorNode.getStartOffset(), 0); |
| } |
| |
| /** |
| * Allowing the INodeAdapter to compare itself against the type |
| * allows it to return true in more than one case. |
| */ |
| public boolean isAdapterForType(Object type) { |
| return ((type == HTMLAttributeValidator.class) || super.isAdapterForType(type)); |
| } |
| |
| /** |
| */ |
| public void validate(IndexedRegion node) { |
| Element target = (Element) node; |
| if (CMUtil.isForeign(target)) |
| return; |
| CMElementDeclaration edec = CMUtil.getDeclaration(target); |
| if (edec == null) |
| return; |
| CMNamedNodeMap declarations = edec.getAttributes(); |
| |
| NamedNodeMap attrs = target.getAttributes(); |
| for (int i = 0; i < attrs.getLength(); i++) { |
| int rgnType = REGION_NAME; |
| int state = ErrorState.NONE_ERROR; |
| Attr a = (Attr) attrs.item(i); |
| // D203637; If the target attr has prefix, the validator should not |
| // warn about it. That is, just ignore. It is able to check whether |
| // an attr has prefix or not by calling XMLAttr#isGlobalAttr(). |
| // When a attr has prefix (not global), it returns false. |
| boolean isXMLAttr = a instanceof XMLAttr; |
| if (isXMLAttr) { |
| XMLAttr xmlattr = (XMLAttr) a; |
| if (!xmlattr.isGlobalAttr()) |
| continue; // skip futher validation and begin next loop. |
| } |
| |
| CMAttributeDeclaration adec = (CMAttributeDeclaration) declarations.getNamedItem(a.getName()); |
| if (adec == null) { |
| // No attr declaration was found. That is, the attr name is undefined. |
| // but not regard it as undefined name if it includes JSP |
| if (!FMUtil.hasJSPRegion(((XMLNode) a).getNameRegion())) { |
| rgnType = REGION_NAME; |
| state = ErrorState.UNDEFINED_NAME_ERROR; |
| } |
| } |
| else { |
| // The attr declaration was found. |
| // At 1st, the name should be checked. |
| if (CMUtil.isHTML(edec) && (!CMUtil.isXHTML(edec))) { |
| // If the target element is pure HTML (not XHTML), some attributes |
| // might be written in boolean format. It should be check specifically. |
| if (CMUtil.isBooleanAttr(adec) && ((XMLAttr) a).hasNameOnly()) |
| continue; // OK, keep going. No more check is needed against this attr. |
| } |
| else { |
| // If the target is other than pure HTML (JSP or XHTML), the name |
| // must be checked exactly (ie in case sensitive way). |
| String actual = a.getName(); |
| String desired = adec.getAttrName(); |
| if (!actual.equals(desired)) { // case mismatch |
| rgnType = REGION_NAME; |
| state = ErrorState.MISMATCHED_ERROR; |
| } |
| } |
| // Then, the value must be checked. |
| if (state == ErrorState.NONE_ERROR) { // Need more check. |
| // Now, the value should be checked, if the type is ENUM. |
| CMDataType attrType = adec.getAttrType(); |
| String actualValue = a.getValue(); |
| if (attrType.getImpliedValueKind() == CMDataType.IMPLIED_VALUE_FIXED) { |
| // Check FIXED value. |
| String validValue = attrType.getImpliedValue(); |
| if (!actualValue.equals(validValue)) { |
| rgnType = REGION_VALUE; |
| state = ErrorState.UNDEFINED_VALUE_ERROR; |
| } |
| } |
| else { |
| String[] candidates = attrType.getEnumeratedValues(); |
| if (candidates != null && candidates.length > 0) { |
| // several candidates are found. |
| boolean found = false; |
| for (int index = 0; index < candidates.length; index++) { |
| String candidate = candidates[index]; |
| // At 1st, compare ignoring case. |
| if (actualValue.equalsIgnoreCase(candidate)) { |
| found = true; |
| if (CMUtil.isCaseSensitive(edec) && (!actualValue.equals(candidate))) { |
| rgnType = REGION_VALUE; |
| state = ErrorState.MISMATCHED_VALUE_ERROR; |
| } |
| break; // exit the loop. |
| } |
| } |
| if (!found) { |
| // No candidate was found. That is, actualValue is invalid. |
| // but not regard it as undefined value if it includes JSP. |
| if (!FMUtil.hasJSPRegion(((XMLNode) a).getValueRegion())) { |
| rgnType = REGION_VALUE; |
| state = ErrorState.UNDEFINED_VALUE_ERROR; |
| } |
| } |
| } |
| } |
| } |
| //<<D210422 |
| if (state == ErrorState.NONE_ERROR) { // Need more check. |
| if (isXMLAttr) { |
| String source = ((XMLAttr) a).getValueRegionText(); |
| if (source != null) { |
| char firstChar = source.charAt(0); |
| char lastChar = source.charAt(source.length() - 1); |
| if (isQuote(firstChar) || isQuote(lastChar)) { |
| if (lastChar != firstChar) { |
| rgnType = REGION_VALUE; |
| state = ErrorState.UNCLOSED_ATTR_VALUE; |
| } |
| } |
| } |
| } |
| } |
| //D210422 |
| } |
| if (state != ErrorState.NONE_ERROR) { |
| Segment seg = getErrorSegment((XMLNode) a, rgnType); |
| if (seg != null) |
| reporter.report(MessageFactory.createMessage(new ErrorInfoImpl(state, seg, a))); |
| } |
| } |
| } |
| |
| //<<D214022 |
| private boolean isQuote(char c) { |
| return (c == SINGLE_QUOTE) || (c == DOUBLE_QUOTE); |
| } |
| //D210422 |
| } |