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}