| /******************************************************************************* |
| * Copyright (c) 2001, 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.xml.ui.reconcile; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.wst.sse.core.IModelManager; |
| import org.eclipse.wst.sse.core.IStructuredModel; |
| import org.eclipse.wst.sse.core.IndexedRegion; |
| import org.eclipse.wst.sse.core.StructuredModelManager; |
| import org.eclipse.wst.validation.core.IFileDelta; |
| import org.eclipse.wst.validation.core.IHelper; |
| import org.eclipse.wst.validation.core.IMessage; |
| import org.eclipse.wst.validation.core.IMessageAccess; |
| import org.eclipse.wst.validation.core.IReporter; |
| import org.eclipse.wst.validation.core.IValidator; |
| import org.eclipse.wst.validation.core.MessageLimitException; |
| import org.eclipse.wst.validation.core.ValidationException; |
| import org.eclipse.wst.xml.core.document.XMLAttr; |
| import org.eclipse.wst.xml.core.document.XMLDocument; |
| import org.eclipse.wst.xml.core.document.XMLElement; |
| import org.eclipse.wst.xml.core.document.XMLModel; |
| import org.eclipse.wst.xml.core.document.XMLNode; |
| import org.eclipse.wst.xml.core.document.XMLText; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| |
| /** |
| * A DelegatingReconcileValidator calls its delegate validator |
| * to get a list of validation error IMessages. Using information |
| * in this IMessage the DelegatingReconcileValidator sets |
| * an offset and a length that should be "squiggled" |
| * |
| * @author Mark Hutchinson |
| * |
| */ |
| public class DelegatingReconcileValidator implements IValidator |
| { //these are the selection Strategies: |
| public static final String ATTRIBUTE = "ATTRIBUTE"; |
| public static final String ATTRIBUTE_NAME ="ATTRIBUTE_NAME"; |
| public static final String ATTRIBUTE_VALUE = "ATTRIBUTE_VALUE"; |
| public static final String START_TAG = "START_TAG"; |
| public static final String TEXT = "TEXT"; |
| public static final String NAME_OF_ATTRIBUTE_WITH_GIVEN_VALUE = "NAME_OF_ATTRIBUTE_WITH_GIVEN_VALUE"; |
| |
| public DelegatingReconcileValidator() |
| { super(); //constructor |
| } |
| |
| public void cleanup(IReporter arg0) |
| { // don't need to implement |
| } |
| |
| // My Implementation of IHelper |
| class MyHelper implements IHelper |
| { |
| InputStream inputStream; |
| |
| IFile file; |
| |
| public MyHelper(InputStream inputStream, IFile file) |
| { this.inputStream = inputStream; |
| this.file = file; |
| } |
| |
| public Object loadModel(String symbolicName, Object[] parms) |
| { if (symbolicName.equals("getFile")) |
| { return file; |
| } |
| return null; |
| } |
| |
| public Object loadModel(String symbolicName) |
| { if (symbolicName.equals("inputStream")) |
| { return inputStream; |
| } |
| return null; |
| } |
| } |
| //My Implementation of IReporter |
| class MyReporter implements IReporter |
| { |
| List list = new ArrayList(); |
| |
| public MyReporter() |
| { super(); |
| } |
| |
| public void addMessage(IValidator origin, IMessage message) throws MessageLimitException |
| { list.add(message); |
| } |
| |
| public void displaySubtask(IValidator validator, IMessage message) |
| {//do not need to implement |
| } |
| |
| public IMessageAccess getMessageAccess() |
| { return null; //do not need to implement |
| } |
| |
| public boolean isCancelled() |
| { return false; //do not need to implement |
| } |
| |
| public void removeAllMessages(IValidator origin, Object object) |
| { //do not need to implement |
| } |
| |
| public void removeAllMessages(IValidator origin) |
| {//do not need to implement |
| } |
| |
| public void removeMessageSubset(IValidator validator, Object obj, String groupName) |
| {//do not need to implement |
| } |
| } |
| |
| protected IValidator getDelegateValidator() |
| { return null;//this class must be overridden by a subclass |
| } |
| |
| /** |
| * Calls a delegate validator getting and updates it's list of ValidationMessages |
| * with a good squiggle offset and length. |
| * |
| * @param helper |
| * loads an object. |
| * @param reporter |
| * Is an instance of an IReporter interface, which is used for |
| * interaction with the user. |
| * @param delta |
| * an array containing the file to be validated. Only position 0 will be validated |
| */ |
| public void validate(IHelper helper, IReporter reporter, IFileDelta[] delta) throws ValidationException |
| { |
| if (delta.length > 0) |
| { |
| // get the file, model and document: |
| IFile file = getFile(delta[0]); |
| XMLModel xmlModel = getModelForResource(file); |
| try |
| { |
| XMLDocument document = xmlModel.getDocument(); |
| |
| // store the file to a byte array: |
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024); |
| xmlModel.save(outputStream); |
| byte[] byteArray = outputStream.toByteArray(); |
| |
| //Get the Validator: |
| IValidator validator = getDelegateValidator(); |
| if (validator != null) |
| { |
| //Validate the file: |
| IHelper vHelper = new MyHelper(new ByteArrayInputStream(byteArray), file); |
| MyReporter vReporter = new MyReporter(); |
| validator.validate(vHelper, vReporter, delta); |
| List messages = vReporter.list; |
| |
| //set the offset and length |
| updateValidationMessages(messages, document, reporter); |
| } |
| } |
| catch (Exception e) |
| { //e.printStackTrace(); |
| } |
| finally |
| { |
| if (xmlModel != null) |
| xmlModel.releaseFromRead(); |
| } |
| } |
| } |
| |
| /** |
| * iterates through the messages and calculates a "better" offset and length |
| * |
| * @param messages - a List of IMessages |
| * @param document - the document |
| * @param reporter - the reporter the messages are to be added to |
| */ |
| protected void updateValidationMessages(List messages, XMLDocument document, IReporter reporter) |
| { |
| for (int i = 0; i < messages.size(); i++) |
| { |
| IMessage message = (IMessage) messages.get(i); |
| try |
| { //get the extra information stored in the parameters: |
| String[] params = message.getParams(); |
| int column = new Integer(params[0]).intValue(); |
| String key = params[1]; |
| String nameOrValue = params[2];//The name or value of what should be squiggled |
| |
| // convert the line and Column numbers to an offset: |
| int start = document.getStructuredDocument().getLineOffset(message.getLineNo() - 1) + column - 1; |
| |
| // calculate the "better" start and end offset: |
| int[] result = computeStartEndLocation(start, message.getText(), key, nameOrValue, document); |
| if (result != null) |
| { |
| message.setOffset(result[0]); |
| message.setLength(result[1] - result[0]); |
| reporter.addMessage(this, message); |
| } |
| } |
| catch (Exception e) |
| { //e.printStackTrace(); |
| } |
| } |
| } |
| /** |
| * @param delta the IFileDelta containing the file name to get |
| * @return the IFile |
| */ |
| public IFile getFile(IFileDelta delta) |
| { |
| IResource res = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(delta.getFileName())); |
| return res instanceof IFile ? (IFile) res : null; |
| } |
| /** |
| * |
| * @param file the file to get the model for |
| * @return the file's XMLModel |
| */ |
| protected XMLModel getModelForResource(IFile file) |
| { |
| IStructuredModel model = null; |
| IModelManager manager = StructuredModelManager.getModelManager(); |
| |
| try |
| { model = manager.getModelForRead(file); |
| // TODO.. HTML validator tries again to get a model a 2nd way |
| } |
| catch (Exception e) |
| { |
| e.printStackTrace(); |
| } |
| |
| return model instanceof XMLModel ? (XMLModel) model : null; |
| } |
| |
| /** |
| * Calculates the "better" offsets. |
| * |
| * @param startOffset - the offset given by Xerces |
| * @param errorMessage - the Xerces error Message |
| * @param key - the selectionStrategy |
| * @param document - the document |
| * @return int[] - position 0 has the start offset of the squiggle range, position 1 has the endOffset |
| */ |
| /* |
| * The way the offsets is calculated is: |
| * |
| * - find the indexed region (element) closest to the given offset |
| * - if we are between two elements, the one on the left is the one we want |
| * - based on the key select a strategy to find the better offset |
| * - use information from nameOrValue and the DOM to get better offsets |
| * |
| */ |
| protected int[] computeStartEndLocation(int startOffset, String errorMessage, String key, String nameOrValue, XMLDocument document) |
| { |
| try |
| { |
| int startEndPositions[] = new int[2]; |
| |
| IndexedRegion region = document.getModel().getIndexedRegion(startOffset); |
| IndexedRegion prevRegion = document.getModel().getIndexedRegion(startOffset - 1); |
| |
| if (prevRegion != region) |
| { // if between two regions, the one on the left is the one we are interested in |
| region = prevRegion; |
| } |
| |
| // initialize start and end positions to be the start positions this means if the |
| // special case is not taken care of below the start and end offset are set to be |
| // the start of the region where the error was |
| startEndPositions[0] = region.getStartOffset(); |
| startEndPositions[1] = startEndPositions[0]; |
| |
| if (region instanceof Node) |
| { |
| Node node = (Node) region; |
| |
| if (key.equals(START_TAG)) |
| {// then we want to highlight the opening tag |
| if (node.getNodeType() == Node.ELEMENT_NODE) |
| { |
| XMLElement element = (XMLElement) node; |
| startEndPositions[0] = element.getStartOffset() + 1; |
| startEndPositions[1] = startEndPositions[0] + element.getTagName().length(); |
| } |
| } |
| else if (key.equals(ATTRIBUTE_NAME)) |
| { // in this case we want to underline the offending attribute name |
| if (node.getNodeType() == Node.ELEMENT_NODE) |
| { |
| XMLElement element = (XMLElement) node; |
| XMLNode attributeNode = (XMLNode) (element.getAttributeNode(nameOrValue)); |
| if (attributeNode != null) |
| { |
| startEndPositions[0] = attributeNode.getStartOffset(); |
| startEndPositions[1] = attributeNode.getStartOffset() + nameOrValue.length(); |
| } |
| } |
| } |
| else if (key.equals(ATTRIBUTE_VALUE)) |
| {// in this case we want to underline the attribute's value |
| if (node.getNodeType() == Node.ELEMENT_NODE) |
| { |
| XMLElement element = (XMLElement) node; |
| XMLAttr attributeNode = (XMLAttr) (element.getAttributeNode(nameOrValue)); |
| if (attributeNode != null) |
| { |
| startEndPositions[0] = attributeNode.getValueRegionStartOffset(); |
| startEndPositions[1] = startEndPositions[0] + attributeNode.getValueRegionText().length(); |
| } |
| } |
| } |
| else if (key.equals(TEXT)) |
| {// in this case we want to underline the text (but not any extra whitespace) |
| if (node.getNodeType() == Node.TEXT_NODE) |
| { |
| XMLText textNode = (XMLText) node; |
| int start = textNode.getStartOffset(); |
| String value = textNode.getNodeValue(); |
| int index = 0; |
| char curChar = value.charAt(index); |
| //here we are finding start offset by skipping over whitespace: |
| while (curChar == '\n' || curChar == '\t' || curChar == '\r' || curChar == ' ') |
| { |
| curChar = value.charAt(index); |
| index++; |
| start++; |
| } |
| startEndPositions[0] = start - 1; |
| startEndPositions[1] = start + value.trim().length(); |
| } |
| else if (node.getNodeType() == Node.ELEMENT_NODE) |
| { |
| XMLElement element = (XMLElement) node; |
| Node child = element.getFirstChild(); |
| if (child instanceof XMLNode) |
| { |
| XMLNode xmlChild = ((XMLNode) child); |
| startEndPositions[0] = xmlChild.getStartOffset(); |
| startEndPositions[1] = xmlChild.getEndOffset(); |
| } |
| } |
| } |
| |
| else if (key.equals(NAME_OF_ATTRIBUTE_WITH_GIVEN_VALUE)) |
| {//underline the name of the attribute containing the given value |
| if (node.getNodeType() == Node.ELEMENT_NODE) |
| { |
| //here we will search through all attributes for the one with the |
| //with the value we want: |
| NamedNodeMap attributes = node.getAttributes(); |
| for (int i=0; i<attributes.getLength(); i++) |
| { |
| XMLAttr attr = (XMLAttr)attributes.item(i); |
| String nodeValue = attr.getNodeValue().trim(); |
| if (nodeValue.equals(nameOrValue)) |
| { startEndPositions[0] = attr.getValueRegionStartOffset() + 1; |
| startEndPositions[1] = startEndPositions[0] + nodeValue.length(); |
| break; |
| } |
| } |
| } |
| } |
| } |
| return startEndPositions; |
| } |
| catch (Exception e) |
| { //e.printStackTrace(); |
| } |
| return null; |
| } |
| } |