001package org.hl7.fhir.r4.utils.formats; 002 003import java.util.Stack; 004 005import org.w3c.dom.Document; 006import org.w3c.dom.Element; 007import org.w3c.dom.Node; 008import org.w3c.dom.UserDataHandler; 009import org.w3c.dom.events.Event; 010import org.w3c.dom.events.EventListener; 011import org.w3c.dom.events.EventTarget; 012import org.w3c.dom.events.MutationEvent; 013import org.xml.sax.Attributes; 014import org.xml.sax.Locator; 015import org.xml.sax.SAXException; 016import org.xml.sax.XMLReader; 017import org.xml.sax.helpers.LocatorImpl; 018import org.xml.sax.helpers.XMLFilterImpl; 019 020// http://javacoalface.blogspot.com.au/2011/04/line-and-column-numbers-in-xml-dom.html 021 022public class XmlLocationAnnotator extends XMLFilterImpl { 023 024 private Locator locator; 025 private Stack<Locator> locatorStack = new Stack<Locator>(); 026 private Stack<Element> elementStack = new Stack<Element>(); 027 private UserDataHandler dataHandler = new LocationDataHandler(); 028 029 public XmlLocationAnnotator(XMLReader xmlReader, Document dom) { 030 super(xmlReader); 031 032 // Add listener to DOM, so we know which node was added. 033 EventListener modListener = new EventListener() { 034 @Override 035 public void handleEvent(Event e) { 036 EventTarget target = ((MutationEvent) e).getTarget(); 037 elementStack.push((Element) target); 038 } 039 }; 040 ((EventTarget) dom).addEventListener("DOMNodeInserted", modListener, true); 041 } 042 043 @Override 044 public void setDocumentLocator(Locator locator) { 045 super.setDocumentLocator(locator); 046 this.locator = locator; 047 } 048 049 @Override 050 public void startElement(String uri, String localName, 051 String qName, Attributes atts) throws SAXException { 052 super.startElement(uri, localName, qName, atts); 053 054 // Keep snapshot of start location, 055 // for later when end of element is found. 056 locatorStack.push(new LocatorImpl(locator)); 057 } 058 059 @Override 060 public void endElement(String uri, String localName, String qName) 061 throws SAXException { 062 063 // Mutation event fired by the adding of element end, 064 // and so lastAddedElement will be set. 065 super.endElement(uri, localName, qName); 066 067 if (locatorStack.size() > 0) { 068 Locator startLocator = locatorStack.pop(); 069 070 XmlLocationData location = new XmlLocationData( 071 startLocator.getSystemId(), 072 startLocator.getLineNumber(), 073 startLocator.getColumnNumber(), 074 locator.getLineNumber(), 075 locator.getColumnNumber()); 076 Element lastAddedElement = elementStack.pop(); 077 078 lastAddedElement.setUserData( 079 XmlLocationData.LOCATION_DATA_KEY, location, 080 dataHandler); 081 } 082 } 083 084 // Ensure location data copied to any new DOM node. 085 private class LocationDataHandler implements UserDataHandler { 086 087 @Override 088 public void handle(short operation, String key, Object data, 089 Node src, Node dst) { 090 091 if (src != null && dst != null) { 092 XmlLocationData locatonData = (XmlLocationData) 093 src.getUserData(XmlLocationData.LOCATION_DATA_KEY); 094 095 if (locatonData != null) { 096 dst.setUserData(XmlLocationData.LOCATION_DATA_KEY, 097 locatonData, dataHandler); 098 } 099 } 100 } 101 } 102}