/*******************************************************************************
* 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.apache.sling.scripting.sightly.impl.html.dom.template;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import org.apache.sling.scripting.sightly.impl.html.dom.AttributeList;
import org.apache.sling.scripting.sightly.impl.html.dom.DocumentHandler;
import org.apache.sling.scripting.sightly.impl.html.dom.HtmlParser;
/**
* The template parser parses an HTML document and returns a reusable tree
* representation.
*/
public class TemplateParser {
/**
* Parse an html document
* @param reader to be parsed
* @throws IOException in case of any parsing error
*
* @return a Template
*/
public Template parse(final Reader reader) throws IOException {
final TemplateParserContext context = new TemplateParserContext();
HtmlParser.parse(reader, context);
return context.getTemplate();
}
public static final class TemplateParserContext implements DocumentHandler {
/** Used for text/character events */
private StringBuilder textBuilder = new StringBuilder();
/** Element stack - root is the Template */
private final Deque<TemplateElementNode> elementStack = new ArrayDeque<TemplateElementNode>();
/** The template. */
private Template template;
public Template getTemplate() {
return this.template;
}
public void onStart() throws IOException {
this.template = new Template();
this.elementStack.push(this.template);
}
public void onEnd() throws IOException {
this.checkText();
this.elementStack.clear();
}
private void checkText() {
if (textBuilder.length() > 0) {
elementStack.peek().addChild(new TemplateTextNode(textBuilder.toString()));
this.textBuilder = new StringBuilder();
}
}
public void onStartElement(String name, AttributeList attList, boolean endSlash) {
this.checkText();
final List<TemplateAttribute> attrs = new ArrayList<TemplateAttribute>();
final Iterator<String> iter = attList.attributeNames();
while ( iter.hasNext() ) {
final String aName = iter.next();
final TemplateAttribute attr = new TemplateAttribute(aName, attList.getValue(aName), attList.getQuoteChar(aName));
attrs.add(attr);
}
final TemplateElementNode element = new TemplateElementNode(name, endSlash, attrs);
element.setHasStartElement();
elementStack.peek().addChild(element);
if ( !endSlash ) {
elementStack.push(element);
}
}
public void onEndElement(String name) {
this.checkText();
if (contains(name)) {
TemplateElementNode element = this.elementStack.pop();
while ( !name.equals(element.getName()) ) {
element = this.elementStack.pop();
}
element.setHasEndElement();
} else {
final TemplateElementNode element
= new TemplateElementNode(name, false, new ArrayList<TemplateAttribute>());
elementStack.peek().addChild(element);
element.setHasEndElement();
}
}
public void onCharacters(final char[] ch, final int off, final int len) {
textBuilder.append(ch, off, len);
}
public void onComment(final String text) throws IOException {
this.checkText();
elementStack.peek().addChild(new TemplateCommentNode(text));
}
private boolean contains(String name) {
for (TemplateElementNode elem : this.elementStack) {
if (name.equals(elem.getName())) {
return true;
}
}
return false;
}
}
}