de13c70bdf4a3c30b1986bd702d60f68a6d22f9c
[yangtools.git] / yang / yang-parser-rfc7950 / 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 static java.util.Objects.requireNonNull;
11
12 import java.util.ArrayDeque;
13 import java.util.Deque;
14 import org.opendaylight.yangtools.yang.parser.spi.source.DeclarationInTextSource;
15 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
16 import org.slf4j.Logger;
17 import org.slf4j.LoggerFactory;
18 import org.w3c.dom.Document;
19 import org.w3c.dom.Element;
20 import org.w3c.dom.Node;
21 import org.xml.sax.Attributes;
22 import org.xml.sax.Locator;
23 import org.xml.sax.SAXException;
24 import org.xml.sax.helpers.DefaultHandler;
25
26 // adapted from https://stackoverflow.com/questions/4915422/get-line-number-from-xml-node-java
27 final class StatementSourceReferenceHandler extends DefaultHandler {
28     private static final Logger LOG = LoggerFactory.getLogger(StatementSourceReferenceHandler.class);
29     private static final String USER_DATA_KEY = StatementSourceReference.class.getName();
30
31     private final Deque<Element> stack = new ArrayDeque<>();
32     private final StringBuilder sb = new StringBuilder();
33     private final Document doc;
34     private final String file;
35
36     private Locator documentLocator;
37
38     StatementSourceReferenceHandler(final Document doc, final String file) {
39         this.doc = requireNonNull(doc);
40         this.file = file;
41     }
42
43     static StatementSourceReference extractRef(final Element element) {
44         final Object value = element.getUserData(USER_DATA_KEY);
45         if (value instanceof StatementSourceReference) {
46             return (StatementSourceReference) value;
47         }
48         if (value != null) {
49             LOG.debug("Ignoring {} attached to key {}", value, USER_DATA_KEY);
50         }
51         return null;
52     }
53
54     @Override
55     public void setDocumentLocator(final Locator locator) {
56         // Save the locator, so that it can be used later for line tracking when traversing nodes.
57         this.documentLocator = locator;
58     }
59
60     @Override
61     @SuppressWarnings("checkstyle:parameterName")
62     public void startElement(final String uri, final String localName, final String qName,
63             final Attributes attributes) {
64         addTextIfNeeded();
65         final Element el = doc.createElementNS(uri, qName);
66         for (int i = 0, len = attributes.getLength(); i < len; i++) {
67             el.setAttributeNS(attributes.getURI(i), attributes.getQName(i), attributes.getValue(i));
68         }
69
70         final StatementSourceReference ref = DeclarationInTextSource.atPosition(file, documentLocator.getLineNumber(),
71             documentLocator.getColumnNumber());
72         el.setUserData(USER_DATA_KEY, ref, null);
73         stack.push(el);
74     }
75
76     @Override
77     @SuppressWarnings("checkstyle:parameterName")
78     public void endElement(final String uri, final String localName, final String qName) {
79         addTextIfNeeded();
80         final Element closedEl = stack.pop();
81         Node parentEl = stack.peek();
82         if (parentEl == null) {
83             // root element
84             parentEl = doc;
85         }
86
87         parentEl.appendChild(closedEl);
88     }
89
90     @Override
91     public void characters(final char[] ch, final int start, final int length) throws SAXException {
92         sb.append(ch, start, length);
93     }
94
95     // Outputs text accumulated under the current node
96     private void addTextIfNeeded() {
97         if (sb.length() > 0) {
98             stack.peek().appendChild(doc.createTextNode(sb.toString()));
99             sb.setLength(0);
100         }
101     }
102 }