/******************************************************************************* | |
* Copyright (c) 2010 Sean Muir | |
* 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: | |
* Sean Muir (JKM Software) - initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.mdht.uml.cda.validate; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.Stack; | |
import org.xml.sax.Attributes; | |
import org.xml.sax.ContentHandler; | |
import org.xml.sax.Locator; | |
import org.xml.sax.SAXException; | |
/** | |
* XPathIndexer creates and index of line and column number of all ellements | |
* within an XML document as supplied by the SAX Locator. The locator is not | |
* 100% accurate regarding values but this is currently only used to approximate | |
* mark locations as part of the CDA validation within the eclipse Validator2 | |
* framework. | |
* | |
* @author Sean Muir (JKM Software) | |
* | |
*/ | |
public class XPathIndexer implements ContentHandler { | |
/** | |
* ElementEntry is used to maintain a bottom up stack of elements found within the CDA document. | |
* | |
*/ | |
static private class ElementEntry { | |
final String elementName; | |
final int elementIndex; | |
final ElementEntry elementParent; | |
ElementEntry(String elementName, int elementIndex, ElementEntry next) { | |
this.elementName = elementName; | |
this.elementIndex = elementIndex; | |
this.elementParent = next; | |
} | |
} | |
/** | |
* ElementLocationData is used to cache the location of each element | |
* | |
*/ | |
public static class ElementLocationData { | |
public int line; | |
public int column; | |
public ElementLocationData(int line, int column) { | |
super(); | |
this.line = line; | |
this.column = column; | |
} | |
} | |
private ElementEntry currentXMLElement = null; | |
/* | |
* ELementIndexes Structure supports [Parent][Element][Location*] | |
* Where | |
* Parent is the xpath to elements location | |
* Element is the element name | |
* Location* is sequential array for location for the named element | |
* | |
* It is designed to support an incremental count approach of elements so | |
* <Root> | |
* <Parent> | |
* <Element /> | |
* <Element /> | |
* <Element /> | |
* </Parent> | |
* </Root> | |
* | |
* would have one Parent index pointing to a hashmap | |
* in that hash map you have Element index pointing to an array | |
* in the array you would have 3 sequential locations, one for each element found | |
* | |
* | |
* The structure and indexElement track element instances to supply running total of element document indexes | |
*/ | |
public HashMap<String, HashMap<String, ArrayList<Locator>>> elementIndexes = new HashMap<>(); | |
/* | |
* xpathLocations is a map between xpath and corresponding xml location | |
*/ | |
public HashMap<String, ElementLocationData> xpathLocations = new HashMap<>(); | |
public HashMap<String, ElementLocationData> xpathAttributes = new HashMap<>(); | |
/* | |
* SAX Locator | |
*/ | |
Locator locator = null; | |
public void characters(char[] text, int start, int length) throws SAXException { | |
} | |
public void endDocument() throws SAXException { | |
} | |
public void endElement(String namespace, String local, String name) throws SAXException { | |
currentXMLElement = currentXMLElement.elementParent; | |
} | |
public void endPrefixMapping(String prefix) throws SAXException { | |
} | |
public ElementLocationData getAttributeLocationByValue(String value) { | |
ElementLocationData elementLocationData = null; | |
if (xpathAttributes.containsKey(value)) { | |
elementLocationData = xpathAttributes.get(value); | |
} | |
return elementLocationData; | |
} | |
/** | |
* getElementLocationByPath | |
* Returns ElementLocationData - | |
* Convert xpath to all upper case to avoid some subtle differences in EMF validation and actual xml content | |
* | |
*/ | |
public ElementLocationData getElementLocationByPath(String xpath) { | |
ElementLocationData elementLocationData = null; | |
String upperXPath = xpath.toUpperCase(); | |
if (xpathLocations.containsKey(upperXPath)) { | |
elementLocationData = xpathLocations.get(upperXPath); | |
} | |
return elementLocationData; | |
} | |
private String getXPathFromEntry(ElementEntry elementEntry) { | |
Stack<String> path = new Stack<>(); | |
while (elementEntry != null) { | |
path.push("/" + elementEntry.elementName + "[" + elementEntry.elementIndex + "]"); | |
elementEntry = elementEntry.elementParent; | |
} | |
String result = ""; | |
while (!path.isEmpty()) { | |
result += path.pop(); | |
} | |
return result; | |
} | |
public void ignorableWhitespace(char[] text, int start, int length) throws SAXException { | |
} | |
/* | |
* Populates elementIndexes based on parent and element | |
*/ | |
private int indexElement(String parent, String element) { | |
// if new parent, create index | |
if (!elementIndexes.containsKey(parent)) { | |
elementIndexes.put(parent, new HashMap<String, ArrayList<Locator>>()); | |
} | |
// if new element for parent, create index | |
if (!elementIndexes.get(parent).containsKey(element)) { | |
elementIndexes.get(parent).put(element, new ArrayList<Locator>()); | |
} | |
// add sequential location based on SAX location | |
elementIndexes.get(parent).get(element).add(locator); | |
// return index count - used in ElementEntry to recreate xpath | |
return elementIndexes.get(parent).get(element).size(); | |
} | |
public void processingInstruction(String target, String data) throws SAXException { | |
} | |
public void setDocumentLocator(Locator locator) { | |
this.locator = locator; | |
} | |
public void skippedEntity(String name) throws SAXException { | |
} | |
public void startDocument() throws SAXException { | |
} | |
public void startElement(String namespace, String local, String name, Attributes attrs) throws SAXException { | |
int index = indexElement(getXPathFromEntry(currentXMLElement), name); | |
currentXMLElement = new ElementEntry(name, index, currentXMLElement); | |
ElementLocationData location = new ElementLocationData(locator.getLineNumber(), locator.getColumnNumber()); | |
xpathLocations.put(getXPathFromEntry(currentXMLElement).toUpperCase(), location); | |
for (int actr = 0; actr < attrs.getLength(); actr++) { | |
if (!xpathAttributes.containsKey(attrs.getValue(actr))) { | |
xpathAttributes.put(attrs.getValue(actr), location); | |
} | |
} | |
} | |
public void startPrefixMapping(String prefix, String uri) throws SAXException { | |
} | |
} |