/******************************************************************************* * Copyright (c) 2005, 2009 Spring IDE Developers * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Spring IDE Developers - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.core.io.xml; import java.util.Map; import java.util.WeakHashMap; import org.apache.xerces.parsers.DOMParser; import org.apache.xerces.xni.Augmentations; import org.apache.xerces.xni.NamespaceContext; import org.apache.xerces.xni.QName; import org.apache.xerces.xni.XMLAttributes; import org.apache.xerces.xni.XMLLocator; import org.apache.xerces.xni.XNIException; import org.w3c.dom.Node; import org.xml.sax.SAXException; /** * Extended version of Xerces' DOM parser which adds line numbers to an internal structure that can be queried in the * same thread. * @author Torsten Juergeleit * @author Christian Dupuis */ public class LineNumberPreservingDOMParser extends DOMParser { private static final String START_LINE = "startLine"; private static final String END_LINE = "endLine"; private XMLLocator locator; public LineNumberPreservingDOMParser() throws SAXException { // To access current nodes we have to turn off a feature setFeature(DEFER_NODE_EXPANSION, false); } public static final int getStartLineNumber(Node node) { return getLineNumberFromUserData(node, START_LINE); } public static final int getEndLineNumber(Node node) { return getLineNumberFromUserData(node, END_LINE); } private static int getLineNumberFromUserData(Node node, String key) { return NodeLineNumberAccessor.getLineNumber(node, key); } @Override public void startDocument(XMLLocator locator, String encoding, NamespaceContext namespaceContext, Augmentations augs) throws XNIException { this.locator = locator; super.startDocument(locator, encoding, namespaceContext, augs); addLineNumberToCurrentNode(START_LINE); } @Override public void endDocument(Augmentations augs) throws XNIException { addLineNumberToCurrentNode(END_LINE); super.endDocument(augs); } @Override public void startElement(QName element, XMLAttributes attributes, Augmentations augs) throws XNIException { super.startElement(element, attributes, augs); addLineNumberToCurrentNode(START_LINE); } @Override public void endElement(QName element, Augmentations augs) throws XNIException { addLineNumberToCurrentNode(END_LINE); super.endElement(element, augs); } private void addLineNumberToCurrentNode(String key) throws XNIException { try { Node node = (Node) getProperty(CURRENT_ELEMENT_NODE); if (node != null) { int line = locator.getLineNumber(); NodeLineNumberAccessor.setLineNumber(node, line, key); } } catch (SAXException e) { throw new XNIException(e); } } private static class NodeLineNumberAccessor { private static ThreadLocal<Map<Node, LineNumbers>> LINE_NUMBERS = new ThreadLocal<Map<Node, LineNumbers>>() { protected Map<Node, LineNumbers> initialValue() { return new WeakHashMap<Node, LineNumbers>(); }; }; public static void setLineNumber(Node node, int line, String key) { LineNumbers lineNumbers = null; if (LINE_NUMBERS.get().containsKey(node)) { lineNumbers = LINE_NUMBERS.get().get(node); } else { lineNumbers = new LineNumbers(); LINE_NUMBERS.get().put(node, lineNumbers); } if (START_LINE.equals(key)) { lineNumbers.setStart(line); } else if (END_LINE.equals(key)) { lineNumbers.setEnd(line); } } public static int getLineNumber(Node node, String key) { if (LINE_NUMBERS.get().containsKey(node)) { if (START_LINE.equals(key)) { return LINE_NUMBERS.get().get(node).getStart(); } else if (END_LINE.equals(key)) { return LINE_NUMBERS.get().get(node).getEnd(); } } return -1; } } private static class LineNumbers { private int start = -1; private int end = -1; public int getStart() { return start; } public void setStart(int start) { this.start = start; } public int getEnd() { return end; } public void setEnd(int end) { this.end = end; } } }