[84177] add XML "Squiggle as you type" Validation
diff --git a/bundles/org.eclipse.wst.xml.ui/plugin.xml b/bundles/org.eclipse.wst.xml.ui/plugin.xml
index 2c12893..3c0dfe6 100644
--- a/bundles/org.eclipse.wst.xml.ui/plugin.xml
+++ b/bundles/org.eclipse.wst.xml.ui/plugin.xml
@@ -30,6 +30,8 @@
       <import plugin="org.eclipse.wst.xml.uriresolver"/>
       <import plugin="org.eclipse.wst.xml.core"/>
       <import plugin="org.eclipse.wst.common.ui"/>
+      <import plugin="org.eclipse.wst.validation"/>
+
    </requires>
 
 
@@ -354,6 +356,21 @@
 	   <initializer class="org.eclipse.wst.xml.ui.internal.preferences.XMLUIPreferenceInitializer"/>
 	</extension>
 
+	 <extension
+         point="org.eclipse.wst.sse.ui.reconcileValidator">
+      <validator
+            scope="total"
+            class="org.eclipse.wst.xml.ui.reconcile.DelegatingReconcileValidatorForXML"
+            id="org.eclipse.wst.xml.ui.reconcile.DelegatingReconcileValidatorForXML">
+         <contentTypeIdentifier
+               id="org.eclipse.core.runtime.xml">
+            <partitionType
+                  id="org.eclipse.wst.xml.DEFAULT_XML">
+            </partitionType>
+         </contentTypeIdentifier>      
+      </validator>
+   </extension>
+
 	<!--======================================================================================-->
 	<!-- Document provider for ExternalFileEditorInput                                        -->
 	<!--======================================================================================-->
diff --git a/bundles/org.eclipse.wst.xml.ui/src/org/eclipse/wst/xml/ui/reconcile/DelegatingReconcileValidator.java b/bundles/org.eclipse.wst.xml.ui/src/org/eclipse/wst/xml/ui/reconcile/DelegatingReconcileValidator.java
new file mode 100644
index 0000000..cfebd56
--- /dev/null
+++ b/bundles/org.eclipse.wst.xml.ui/src/org/eclipse/wst/xml/ui/reconcile/DelegatingReconcileValidator.java
@@ -0,0 +1,395 @@
+/*******************************************************************************
+ * 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;
+  }  
+}
diff --git a/bundles/org.eclipse.wst.xml.ui/src/org/eclipse/wst/xml/ui/reconcile/DelegatingReconcileValidatorForXML.java b/bundles/org.eclipse.wst.xml.ui/src/org/eclipse/wst/xml/ui/reconcile/DelegatingReconcileValidatorForXML.java
new file mode 100644
index 0000000..0511d6a
--- /dev/null
+++ b/bundles/org.eclipse.wst.xml.ui/src/org/eclipse/wst/xml/ui/reconcile/DelegatingReconcileValidatorForXML.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * 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 org.eclipse.wst.validation.core.IValidator;
+import org.eclipse.wst.validation.internal.ValidationRegistryReader;
+import org.eclipse.wst.validation.internal.ValidatorMetaData;
+
+/**
+ * @author Mark Hutchinson
+ *
+ */
+public class DelegatingReconcileValidatorForXML extends DelegatingReconcileValidator
+{
+ private final static String VALIDATOR_CLASS = "org.eclipse.wst.xml.validation.internal.ui.eclipse.Validator";
+ 
+  public DelegatingReconcileValidatorForXML()
+  { super();
+  }
+ 
+  protected IValidator getDelegateValidator()
+  {
+    try
+    {
+      //Get the validator:
+      ValidatorMetaData validatorData = ValidationRegistryReader.getReader().getValidatorMetaData(VALIDATOR_CLASS);
+      return validatorData.getValidator();
+    }
+    catch (Exception e)
+    { //
+    }
+    return null;
+  }
+}