001package org.hl7.fhir.r4.utils.formats;
002
003/*-
004 * #%L
005 * org.hl7.fhir.r4
006 * %%
007 * Copyright (C) 2014 - 2019 Health Level 7
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023
024import java.util.Stack;
025
026import org.w3c.dom.Document;
027import org.w3c.dom.Element;
028import org.w3c.dom.Node;
029import org.w3c.dom.UserDataHandler;
030import org.w3c.dom.events.Event;
031import org.w3c.dom.events.EventListener;
032import org.w3c.dom.events.EventTarget;
033import org.w3c.dom.events.MutationEvent;
034import org.xml.sax.Attributes;
035import org.xml.sax.Locator;
036import org.xml.sax.SAXException;
037import org.xml.sax.XMLReader;
038import org.xml.sax.helpers.LocatorImpl;
039import org.xml.sax.helpers.XMLFilterImpl;
040
041// http://javacoalface.blogspot.com.au/2011/04/line-and-column-numbers-in-xml-dom.html
042
043public class XmlLocationAnnotator extends XMLFilterImpl  {
044
045  private Locator locator;
046  private Stack<Locator> locatorStack = new Stack<Locator>();
047  private Stack<Element> elementStack = new Stack<Element>();
048  private UserDataHandler dataHandler = new LocationDataHandler();
049
050  public XmlLocationAnnotator(XMLReader xmlReader, Document dom) {
051      super(xmlReader);
052
053      // Add listener to DOM, so we know which node was added.
054      EventListener modListener = new EventListener() {
055          @Override
056          public void handleEvent(Event e) {
057              EventTarget target = ((MutationEvent) e).getTarget();
058              elementStack.push((Element) target);
059          }
060      };
061      ((EventTarget) dom).addEventListener("DOMNodeInserted", modListener, true);
062  }
063
064  @Override
065  public void setDocumentLocator(Locator locator) {
066      super.setDocumentLocator(locator);
067      this.locator = locator;
068  }
069
070  @Override
071  public void startElement(String uri, String localName,
072          String qName, Attributes atts) throws SAXException {
073      super.startElement(uri, localName, qName, atts);
074
075      // Keep snapshot of start location,
076      // for later when end of element is found.
077      locatorStack.push(new LocatorImpl(locator));
078  }
079
080  @Override
081  public void endElement(String uri, String localName, String qName)
082          throws SAXException {
083
084      // Mutation event fired by the adding of element end,
085      // and so lastAddedElement will be set.
086      super.endElement(uri, localName, qName);
087     
088      if (locatorStack.size() > 0) {
089          Locator startLocator = locatorStack.pop();
090         
091          XmlLocationData location = new XmlLocationData(
092                  startLocator.getSystemId(),
093                  startLocator.getLineNumber(),
094                  startLocator.getColumnNumber(),
095                  locator.getLineNumber(),
096                  locator.getColumnNumber());
097         Element lastAddedElement = elementStack.pop();
098         
099          lastAddedElement.setUserData(
100                  XmlLocationData.LOCATION_DATA_KEY, location,
101                  dataHandler);
102      }
103  }
104
105  // Ensure location data copied to any new DOM node.
106  private class LocationDataHandler implements UserDataHandler {
107
108      @Override
109      public void handle(short operation, String key, Object data,
110              Node src, Node dst) {
111         
112          if (src != null && dst != null) {
113              XmlLocationData locatonData = (XmlLocationData)
114                      src.getUserData(XmlLocationData.LOCATION_DATA_KEY);
115             
116              if (locatonData != null) {
117                  dst.setUserData(XmlLocationData.LOCATION_DATA_KEY,
118                          locatonData, dataHandler);
119              }
120          }
121      }
122  }
123}