4131f5573150ce0371b443fb935e2f945cf8b0b1
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / rfc7950 / 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.rfc7950.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     @SuppressWarnings("checkstyle:parameterName")
77     public void endElement(final String uri, final String localName, final String qName) {
78         addTextIfNeeded();
79         final Element closedEl = stack.pop();
80         Node parentEl = stack.peek();
81         if (parentEl == null) {
82             // root element
83             parentEl = doc;
84         }
85
86         parentEl.appendChild(closedEl);
87     }
88
89     @Override
90     public void characters(final char[] ch, final int start, final int length) throws SAXException {
91         sb.append(ch, start, length);
92     }
93
94     // Outputs text accumulated under the current node
95     private void addTextIfNeeded() {
96         if (sb.length() > 0) {
97             stack.peek().appendChild(doc.createTextNode(sb.toString()));
98             sb.setLength(0);
99         }
100     }
101 }