blob: 42232c4e5374f4ec9aaca16af65679419570ed4b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.html.editor.lib;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Properties;
import org.netbeans.modules.html.editor.lib.api.HtmlSource;
import org.netbeans.modules.html.editor.lib.api.elements.*;
import org.netbeans.modules.html.editor.lib.api.model.HtmlModel;
import org.netbeans.modules.html.editor.lib.api.model.HtmlTag;
import org.netbeans.modules.web.common.api.LexerUtils;
import org.openide.util.Lookup;
import org.openide.util.Parameters;
/**
*
* @author marekfukala
*/
public class XmlSyntaxTreeBuilder {
private static boolean ADD_TEXT_NODES;
private static boolean FOLLOW_HTML_MODEL;
private static HtmlModel MODEL;
public static Node makeUncheckedTree(HtmlSource source, String namespace, Lookup lookup) {
Parameters.notNull("source", source);
Parameters.notNull("lookup", lookup);
ElementsIteratorHandle handle = lookup.lookup(ElementsIteratorHandle.class);
Iterator<Element> elements = handle != null ? handle.getIterator() : null;
assert elements != null;
Properties props = lookup.lookup(Properties.class);
if (props != null) {
ADD_TEXT_NODES = Boolean.parseBoolean(props.getProperty("add_text_nodes")); //NOI18N
FOLLOW_HTML_MODEL = Boolean.parseBoolean(props.getProperty("follow_html_model")); //NOI18N
}
if (FOLLOW_HTML_MODEL) {
MODEL = lookup.lookup(HtmlModel.class);
assert MODEL != null : "add HtmlModel instance to the lookup!"; //NOI18N
}
CharSequence code = source.getSourceCode();
XmlSTElements.Root rootNode = new XmlSTElements.Root(namespace, code);
LinkedList<XmlSTElements.OT> stack = new LinkedList<>();
stack.add(rootNode);
while (elements.hasNext()) {
Element element = elements.next();
if (element.type() == ElementType.OPEN_TAG) { //open tag
OpenTag plainOpenTag = (OpenTag) element;
OpenTag openTagNode = plainOpenTag.isEmpty()
? new XmlSTElements.EmptyOT(
plainOpenTag.attributes(),
plainOpenTag.name(),
code,
plainOpenTag.from(),
plainOpenTag.to())
: new XmlSTElements.OT(
plainOpenTag.attributes(),
plainOpenTag.name(),
code,
plainOpenTag.from(),
plainOpenTag.to());
//add the node to its parent
XmlSTElements.OT peek = stack.getLast();
peek.addChild(openTagNode);
if (MODEL != null) {
HtmlTag htmlTag = MODEL.getTag(plainOpenTag.name().toString());
if (htmlTag != null) {
if (htmlTag.isEmpty()) {
//imply the close tag (do not add to the nesting stack)
continue;
}
}
}
//possible add the node to the nodes stack
if (!(plainOpenTag.isEmpty())) {
stack.addLast((XmlSTElements.OT) openTagNode);
}
} else if (element.type() == ElementType.CLOSE_TAG) { //close tag
CloseTag plainElement = (CloseTag) element;
CharSequence tagName = plainElement.name();
XmlSTElements.ET endTagNode = new XmlSTElements.ET(
plainElement.name(),
code,
plainElement.from(),
plainElement.to());
int matched_index = -1;
for (int i = stack.size() - 1; i >= 0; i--) {
OpenTag node = stack.get(i);
if (LexerUtils.equals(tagName, node.name(), false, false)) {
//ok, match
matched_index = i;
break;
}
}
assert matched_index != 0; //never match root node, either -1 or > 0
if (matched_index > 0) {
//something matched
XmlSTElements.OT match = stack.get(matched_index);
//remove them ALL the left elements from the stack
for (int i = stack.size() - 1; i > matched_index; i--) {
XmlSTElements.OT node = stack.get(i);
node.setLogicalEndOffset(endTagNode.from());
stack.remove(i);
}
//add the node to the proper parent
XmlSTElements.OT match_parent = stack.get(matched_index - 1);
match_parent.addChild(endTagNode);
//wont' help GS at all, but should be ok
match.setMatchingEndTag(endTagNode);
match.setLogicalEndOffset(endTagNode.to());
endTagNode.setMatchingOpenTag(match);
//remove the matched tag from stack
stack.removeLast();
} else {
//add it to the last node
stack.getLast().addChild(endTagNode);
}
} else if (element.type() == ElementType.TEXT) {
if (ADD_TEXT_NODES) {
XmlSTElements.OT parent = stack.getLast();
Element text = new XmlSTElements.Text(
code, element.from(), element.to());
parent.addChild(text);
}
} else {
//rest of the syntax element types
//XXX do we need to have these in the AST???
// add a new AST node to the last node on the stack
// Node.NodeType nodeType = intToNodeType(element.type());
//
// Node node = new Node(null, nodeType, element.offset(),
// element.offset() + element.length(), false);
//
// stack.getLast().addChild(node);
}
}
//check the stack content and resolve left nodes
for (int i = stack.size() - 1; i > 0; i--) { // (i > 0) == do not process the very first (root) node
XmlSTElements.OT node = stack.get(i);
node.setLogicalEndOffset(code.length());
}
return rootNode;
}
}