package org.hl7.fhir.dstu3.utils; import java.util.Stack; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.UserDataHandler; import org.w3c.dom.events.Event; import org.w3c.dom.events.EventListener; import org.w3c.dom.events.EventTarget; import org.w3c.dom.events.MutationEvent; import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.LocatorImpl; import org.xml.sax.helpers.XMLFilterImpl; // http://javacoalface.blogspot.com.au/2011/04/line-and-column-numbers-in-xml-dom.html public class XmlLocationAnnotator extends XMLFilterImpl { private Locator locator; private Stack<Locator> locatorStack = new Stack<Locator>(); private Stack<Element> elementStack = new Stack<Element>(); private UserDataHandler dataHandler = new LocationDataHandler(); public XmlLocationAnnotator(XMLReader xmlReader, Document dom) { super(xmlReader); // Add listener to DOM, so we know which node was added. EventListener modListener = new EventListener() { @Override public void handleEvent(Event e) { EventTarget target = ((MutationEvent) e).getTarget(); elementStack.push((Element) target); } }; ((EventTarget) dom).addEventListener("DOMNodeInserted", modListener, true); } @Override public void setDocumentLocator(Locator locator) { super.setDocumentLocator(locator); this.locator = locator; } @Override public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { super.startElement(uri, localName, qName, atts); // Keep snapshot of start location, // for later when end of element is found. locatorStack.push(new LocatorImpl(locator)); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { // Mutation event fired by the adding of element end, // and so lastAddedElement will be set. super.endElement(uri, localName, qName); if (locatorStack.size() > 0) { Locator startLocator = locatorStack.pop(); XmlLocationData location = new XmlLocationData( startLocator.getSystemId(), startLocator.getLineNumber(), startLocator.getColumnNumber(), locator.getLineNumber(), locator.getColumnNumber()); Element lastAddedElement = elementStack.pop(); lastAddedElement.setUserData( XmlLocationData.LOCATION_DATA_KEY, location, dataHandler); } } // Ensure location data copied to any new DOM node. private class LocationDataHandler implements UserDataHandler { @Override public void handle(short operation, String key, Object data, Node src, Node dst) { if (src != null && dst != null) { XmlLocationData locatonData = (XmlLocationData) src.getUserData(XmlLocationData.LOCATION_DATA_KEY); if (locatonData != null) { dst.setUserData(XmlLocationData.LOCATION_DATA_KEY, locatonData, dataHandler); } } } } }