1ee3f9f38f3ad5b655338e710acbd55c70a8f543
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / rfc6020 / repo / StatementSourceReferenceHandler.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.parser.rfc6020.repo;
9
10 import com.google.common.base.Preconditions;
11 import java.util.ArrayDeque;
12 import java.util.Deque;
13 import org.opendaylight.yangtools.yang.parser.spi.source.DeclarationInTextSource;
14 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
15 import org.slf4j.Logger;
16 import org.slf4j.LoggerFactory;
17 import org.w3c.dom.Document;
18 import org.w3c.dom.Element;
19 import org.w3c.dom.Node;
20 import org.xml.sax.Attributes;
21 import org.xml.sax.Locator;
22 import org.xml.sax.SAXException;
23 import org.xml.sax.helpers.DefaultHandler;
24
25 // adapted from https://stackoverflow.com/questions/4915422/get-line-number-from-xml-node-java
26 final class StatementSourceReferenceHandler extends DefaultHandler {
27     private static final Logger LOG = LoggerFactory.getLogger(StatementSourceReferenceHandler.class);
28     private static final String USER_DATA_KEY = StatementSourceReference.class.getName();
29
30     private final Deque<Element> stack = new ArrayDeque<>();
31     private final StringBuilder sb = new StringBuilder();
32     private final Document doc;
33     private final String file;
34
35     private Locator locator;
36
37     StatementSourceReferenceHandler(final Document doc, final String file) {
38         this.doc = Preconditions.checkNotNull(doc);
39         this.file = file;
40     }
41
42     static StatementSourceReference extractRef(final Element element) {
43         final Object value = element.getUserData(USER_DATA_KEY);
44         if (value instanceof StatementSourceReference) {
45             return (StatementSourceReference) value;
46         }
47         if (value != null) {
48             LOG.debug("Ignoring {} attached to key {}", value, USER_DATA_KEY);
49         }
50         return null;
51     }
52
53     @Override
54     public void setDocumentLocator(final Locator locator) {
55         // Save the locator, so that it can be used later for line tracking when traversing nodes.
56         this.locator = locator;
57     }
58
59     @Override
60     @SuppressWarnings("checkstyle:parameterName")
61     public void startElement(final String uri, final String localName, final String qName,
62             final Attributes attributes) {
63         addTextIfNeeded();
64         final Element el = doc.createElementNS(uri, qName);
65         for (int i = 0, len = attributes.getLength(); i < len; i++) {
66             el.setAttributeNS(attributes.getURI(i), attributes.getQName(i), attributes.getValue(i));
67         }
68
69         final StatementSourceReference ref = DeclarationInTextSource.atPosition(file, locator.getLineNumber(),
70             locator.getColumnNumber());
71         el.setUserData(USER_DATA_KEY, ref, null);
72         stack.push(el);
73     }
74
75     @Override
76     public void endElement(final String uri, final String localName, final String qName) {
77         addTextIfNeeded();
78         final Element closedEl = stack.pop();
79         Node parentEl = stack.peek();
80         if (parentEl == null) {
81             // root element
82             parentEl = doc;
83         }
84
85         parentEl.appendChild(closedEl);
86     }
87
88     @Override
89     public void characters(final char ch[], final int start, final int length) throws SAXException {
90         sb.append(ch, start, length);
91     }
92
93     // Outputs text accumulated under the current node
94     private void addTextIfNeeded() {
95         if (sb.length() > 0) {
96             stack.peek().appendChild(doc.createTextNode(sb.toString()));
97             sb.setLength(0);
98         }
99     }
100 }