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}