You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by fm...@apache.org on 2014/11/28 11:18:09 UTC

svn commit: r1642281 [11/14] - in /sling/trunk/contrib/scripting/sightly: ./ engine/ engine/src/main/antlr4/org/apache/sling/parser/expr/generated/ engine/src/main/antlr4/org/apache/sling/scripting/ engine/src/main/antlr4/org/apache/sling/scripting/sig...

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParser.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParser.java?rev=1642281&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParser.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParser.java Fri Nov 28 10:18:01 2014
@@ -0,0 +1,468 @@
+/*******************************************************************************
+ * 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;
+
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * HTML parser. Invokes a <code>DocumentHandler</code> whenever an event occurs.
+ */
+public final class HtmlParser {
+
+    /** Internal character buffer */
+    private final CharArrayWriter buffer = new CharArrayWriter(256);
+
+    /** Tag tokenizer */
+    private final TagTokenizer tokenizer = new TagTokenizer();
+
+    /** Tag name buffer */
+    private final CharArrayWriter tagNameBuffer = new CharArrayWriter(30);
+
+    /** Tag name */
+    private String tagName;
+
+    /** Registered document handler */
+    private final DocumentHandler documentHandler;
+
+    private enum PARSE_STATE {
+        OUTSIDE,
+        TAG,
+        SCRIPT,
+        COMMENT,
+        STRING,
+        EXPRESSION
+    }
+
+    /** Tag type constant */
+    private final static int TT_NONE = 0;
+
+    /** Tag type constant */
+    private final static int TT_MAYBE = 1;
+
+    /** Tag type constant */
+    private final static int TT_TAG = 2;
+
+    /** Expression state constant */
+    private final static int EXPR_NONE = 0;
+
+    /** Expression state constant */
+    private final static int EXPR_MAYBE = 1;
+
+    /** Parse state */
+    private PARSE_STATE parseState = PARSE_STATE.OUTSIDE;
+
+    /** Parse substate */
+    private int parseSubState;
+
+    /** Previous parse state */
+    private PARSE_STATE prevParseState;
+
+    /** Current tag type */
+    private int tagType;
+
+    /** Expression type */
+    private int exprType;
+
+    /** Quote character */
+    private char quoteChar;
+
+    public static void parse(final Reader reader, final DocumentHandler documentHandler)
+    throws IOException {
+        final HtmlParser parser = new HtmlParser(documentHandler);
+        parser.parse(reader);
+    }
+
+    /**
+     * Default constructor.
+     */
+    private HtmlParser(final DocumentHandler documentHandler) {
+        this.documentHandler = documentHandler;
+    }
+
+    private void parse(final Reader reader)
+    throws IOException {
+        try {
+            this.documentHandler.onStart();
+            final char[] readBuffer = new char[2048];
+            int readLen = 0;
+            while ( (readLen = reader.read(readBuffer)) > 0 ) {
+                this.update(readBuffer, readLen);
+            }
+            this.flushBuffer();
+            this.documentHandler.onEnd();
+        } finally {
+            try {
+                reader.close();
+            } catch ( final IOException ignore) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Feed characters to the parser.
+     *
+     * @param buf character buffer
+     * @param len length of affected buffer
+     */
+    private void update(final char[] buf, int len) throws IOException {
+        int start = 0;
+        final int end = len;
+
+        for (int curr = start; curr < end; curr++) {
+            final char c = buf[curr];
+
+            switch (parseState) {
+            case OUTSIDE:
+                if (c == '<') {
+                    if (curr > start) {
+                        documentHandler.onCharacters(buf, start, curr - start);
+                    }
+                    start = curr;
+                    parseState = PARSE_STATE.TAG;
+                    parseSubState = 0;
+                    tagType = TT_MAYBE;
+                    resetTagName();
+                } else if (c == '$') {
+                    exprType = EXPR_MAYBE;
+                    parseState = PARSE_STATE.EXPRESSION;
+                }
+                break;
+            case TAG:
+                switch (parseSubState) {
+                case -1:
+                    if (c == '"' || c == '\'') {
+                        quoteChar = c;
+                        prevParseState = parseState;
+                        parseState = PARSE_STATE.STRING;
+                        parseSubState = -1;
+                    } else if (c == '>') {
+                        parseState = PARSE_STATE.OUTSIDE;
+                    }
+                    break;
+                case 0:
+                    if (c == '!') {
+                        parseState = PARSE_STATE.COMMENT;
+                        parseSubState = 0;
+                        tagType = TT_NONE;
+                        flushBuffer();
+                    } else if (c == '"' || c == '\'') {
+                        quoteChar = c;
+                        prevParseState = parseState;
+                        parseState = PARSE_STATE.STRING;
+                        parseSubState = -1;
+                        tagType = TT_NONE;
+                        flushBuffer();
+                    } else if (c == '>') {
+                        parseState = PARSE_STATE.OUTSIDE;
+                        tagType = TT_NONE;
+                        flushBuffer();
+                    } else if (!Character.isWhitespace(c)) {
+                        tagNameBuffer.write(c);
+                        parseSubState = 1;
+                    } else {
+                        parseSubState = -1;
+                        tagType = TT_NONE;
+                        flushBuffer();
+                    }
+                    break;
+                case 1:
+                    if (c == '"' || c == '\'') {
+                        tagType = TT_TAG;
+                        parseSubState = 2;
+                        quoteChar = c;
+                        prevParseState = parseState;
+                        parseState = PARSE_STATE.STRING;
+                    } else if (c == '>') {
+                        parseState = processTag(buf, start, curr - start + 1) ? PARSE_STATE.SCRIPT : PARSE_STATE.OUTSIDE;
+                        start = curr + 1;
+                        tagType = TT_NONE;
+                        parseSubState = 0;
+                    } else if (Character.isWhitespace(c)) {
+                        tagType = TT_TAG;
+                        parseSubState = 2;
+                    } else {
+                        tagNameBuffer.write(c);
+                    }
+                    break;
+                case 2:
+                    if (c == '"' || c == '\'') {
+                        quoteChar = c;
+                        prevParseState = parseState;
+                        parseState = PARSE_STATE.STRING;
+                    } else if (c == '>') {
+                        if (tagType == TT_TAG) {
+                            parseState = processTag(buf, start, curr - start + 1) ? PARSE_STATE.SCRIPT : PARSE_STATE.OUTSIDE;
+                            start = curr + 1;
+                        } else {
+                            flushBuffer();
+                            parseState = "SCRIPT".equalsIgnoreCase(getTagName()) ? PARSE_STATE.SCRIPT : PARSE_STATE.OUTSIDE;
+                        }
+                        tagType = TT_NONE;
+                        parseSubState = 0;
+                    }
+                    break;
+                }
+                break;
+            case COMMENT:
+                switch (parseSubState) {
+                case 0:
+                    if (c == '-') {
+                        parseSubState++;
+                    } else if (c == '"' || c == '\'') {
+                        quoteChar = c;
+                        prevParseState = PARSE_STATE.TAG;
+                        parseState = PARSE_STATE.STRING;
+                        parseSubState = -1;
+                        tagType = TT_NONE;
+                        flushBuffer();
+                    } else if (c == '>') {
+                        parseState = PARSE_STATE.OUTSIDE;
+                        tagType = TT_NONE;
+                        flushBuffer();
+                    } else {
+                        parseState = PARSE_STATE.TAG;
+                        parseSubState = -1;
+                        tagType = TT_NONE;
+                        flushBuffer();
+                    }
+                    break;
+                case 1:
+                    if (c == '-') {
+                        parseSubState++;
+                    } else if (c == '"' || c == '\'') {
+                        quoteChar = c;
+                        prevParseState = PARSE_STATE.TAG;
+                        parseState = PARSE_STATE.STRING;
+                        parseSubState = -1;
+                        tagType = TT_NONE;
+                        flushBuffer();
+                    } else if (c == '>') {
+                        parseState = PARSE_STATE.OUTSIDE;
+                        tagType = TT_NONE;
+                        flushBuffer();
+                    } else {
+                        parseState = PARSE_STATE.TAG;
+                        parseSubState = -1;
+                        tagType = TT_NONE;
+                        flushBuffer();
+                    }
+                    break;
+                case 2:
+                    if (c == '-') {
+                        parseSubState++;
+                    }
+                    break;
+                case 3:
+                    if (c == '-') {
+                        parseSubState++;
+                    } else {
+                        parseSubState = 2;
+                    }
+                    break;
+                case 4:
+                    if (c == '>') {
+                        parseState = PARSE_STATE.OUTSIDE;
+                        documentHandler.onComment(new String(buf, start, curr - start + 1));
+                        start = curr + 1;
+                    } else {
+                        parseSubState = 2;
+                    }
+                    break;
+                }
+                break;
+
+            case SCRIPT:
+                switch (parseSubState) {
+                case 0:
+                    if (c == '<') {
+                        if (curr > start) {
+                            documentHandler.onCharacters(buf, start, curr - start);
+                        }
+                        start = curr;
+                        tagType = TT_MAYBE;
+                        parseSubState++;
+                    }
+                    break;
+                case 1:
+                    if (c == '/') {
+                        parseSubState++;
+                    } else {
+                        tagType = TT_NONE;
+                        parseSubState = 0;
+                        flushBuffer();
+                    }
+                    break;
+                case 2:
+                    if (c == 'S' || c == 's') {
+                        parseSubState++;
+                    } else {
+                        tagType = TT_NONE;
+                        parseSubState = 0;
+                        flushBuffer();
+                    }
+                    break;
+                case 3:
+                    if (c == 'C' || c == 'c') {
+                        parseSubState++;
+                    } else {
+                        tagType = TT_NONE;
+                        parseSubState = 0;
+                        flushBuffer();
+                    }
+                    break;
+                case 4:
+                    if (c == 'R' || c == 'r') {
+                        parseSubState++;
+                    } else {
+                        tagType = TT_NONE;
+                        parseSubState = 0;
+                        flushBuffer();
+                    }
+                    break;
+                case 5:
+                    if (c == 'I' || c == 'i') {
+                        parseSubState++;
+                    } else {
+                        tagType = TT_NONE;
+                        parseSubState = 0;
+                        flushBuffer();
+                    }
+                    break;
+                case 6:
+                    if (c == 'P' || c == 'p') {
+                        parseSubState++;
+                    } else {
+                        tagType = TT_NONE;
+                        parseSubState = 0;
+                        flushBuffer();
+                    }
+                    break;
+                case 7:
+                    if (c == 'T' || c == 't') {
+                        parseSubState++;
+                    } else {
+                        tagType = TT_NONE;
+                        parseSubState = 0;
+                        flushBuffer();
+                    }
+                    break;
+                case 8:
+                    if (c == '>') {
+                        processTag(buf, start, curr - start + 1);
+                        start = curr + 1;
+                        tagType = TT_NONE;
+                        parseState = PARSE_STATE.OUTSIDE;
+                    }
+                    break;
+                }
+                break;
+
+            case STRING:
+                if (c == quoteChar) {
+                    parseState = prevParseState;
+                }
+                break;
+
+            case EXPRESSION:
+                if (exprType == EXPR_MAYBE && c != '{') {
+                    // not a valid expression
+                    if (c == '<') {
+                        //reset to process tag correctly
+                        curr--;
+                    }
+                    parseState = PARSE_STATE.OUTSIDE;
+                } else if (c == '}') {
+                    parseState = PARSE_STATE.OUTSIDE;
+                }
+                exprType = EXPR_NONE;
+                break;
+            }
+        }
+        if (start < end) {
+            if (tagType == TT_NONE) {
+                documentHandler.onCharacters(buf, start, end - start);
+            } else {
+                buffer.write(buf, start, end - start);
+            }
+        }
+    }
+
+    /**
+     * Clears the internal tagname buffer and cache
+     */
+    private void resetTagName() {
+        tagName = null;
+        tagNameBuffer.reset();
+    }
+
+    /**
+     * Returns the tagname scanned and resets the internal tagname buffer
+     *
+     * @return tagname
+     */
+    private String getTagName() {
+        if (tagName == null) {
+            tagName = tagNameBuffer.toString();
+        }
+        return tagName;
+    }
+
+    /**
+     * Flush internal buffer. This forces the parser to flush the characters
+     * still held in its internal buffer, if the parsing state allows.
+     */
+    private void flushBuffer() throws IOException {
+        if (buffer.size() > 0) {
+            final char[] chars = buffer.toCharArray();
+            documentHandler.onCharacters(chars, 0, chars.length);
+            buffer.reset();
+        }
+    }
+
+    /**
+     * Decompose a tag and feed it to the document handler.
+     *
+     * @param ch
+     *            character data
+     * @param off
+     *            offset where character data starts
+     * @param len
+     *            length of character data
+     */
+    private boolean processTag(char[] ch, int off, int len) throws IOException {
+        buffer.write(ch, off, len);
+
+        final char[] snippet = buffer.toCharArray();
+
+        tokenizer.tokenize(snippet, 0, snippet.length);
+        if (!tokenizer.endTag()) {
+            documentHandler.onStartElement(tokenizer.tagName(), tokenizer
+                    .attributes(), tokenizer
+                    .endSlash());
+        } else {
+            documentHandler.onEndElement(tokenizer.tagName());
+        }
+
+        buffer.reset();
+        return "SCRIPT".equalsIgnoreCase(tokenizer.tagName()) && !tokenizer.endSlash();
+    }
+}

Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParser.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParserService.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParserService.java?rev=1642281&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParserService.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParserService.java Fri Nov 28 10:18:01 2014
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * 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;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.scripting.sightly.impl.html.dom.template.Template;
+import org.apache.sling.scripting.sightly.impl.html.dom.template.TemplateParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+@Properties({
+        @Property(name = "service.description", value = "Sightly Simple HTML parser"),
+        @Property(name = "service.ranking", intValue = 1000)
+})
+@Service(HtmlParserService.class)
+public class HtmlParserService {
+
+    private static final Logger log = LoggerFactory.getLogger(HtmlParserService.class);
+
+    /**
+     * Parse the given document and use the handler to process
+     * the markup events
+     *
+     * @param document - the parsed document
+     * @param handler  - a markup handler
+     */
+    public void parse(String document, MarkupHandler handler) {
+        try {
+            final StringReader sr = new StringReader(document);
+            final TemplateParser parser = new TemplateParser();
+            final Template template = parser.parse(sr);
+            // walk through the tree and send events
+            TreeTraverser tree = new TreeTraverser(handler);
+            tree.traverse(template);
+        } catch (IOException e) {
+            log.error("Failed to parse Sightly template", e);
+        }
+    }
+}

Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParserService.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java?rev=1642281&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java Fri Nov 28 10:18:01 2014
@@ -0,0 +1,406 @@
+/*******************************************************************************
+ * 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;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Stack;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.sling.scripting.sightly.impl.compiler.Syntax;
+import org.apache.sling.scripting.sightly.impl.compiler.frontend.CompilerContext;
+import org.apache.sling.scripting.sightly.impl.compiler.frontend.ElementContext;
+import org.apache.sling.scripting.sightly.impl.compiler.frontend.ExpressionParser;
+import org.apache.sling.scripting.sightly.impl.compiler.frontend.ExpressionWrapper;
+import org.apache.sling.scripting.sightly.impl.compiler.frontend.Fragment;
+import org.apache.sling.scripting.sightly.impl.compiler.frontend.Interpolation;
+import org.apache.sling.scripting.sightly.impl.filter.Filter;
+import org.apache.sling.scripting.sightly.impl.compiler.expression.Expression;
+import org.apache.sling.scripting.sightly.impl.compiler.expression.ExpressionNode;
+import org.apache.sling.scripting.sightly.impl.compiler.expression.node.BinaryOperation;
+import org.apache.sling.scripting.sightly.impl.compiler.expression.node.BinaryOperator;
+import org.apache.sling.scripting.sightly.impl.compiler.expression.node.BooleanConstant;
+import org.apache.sling.scripting.sightly.impl.compiler.expression.node.Identifier;
+import org.apache.sling.scripting.sightly.impl.compiler.expression.node.StringConstant;
+import org.apache.sling.scripting.sightly.impl.plugin.MarkupContext;
+import org.apache.sling.scripting.sightly.impl.plugin.Plugin;
+import org.apache.sling.scripting.sightly.impl.plugin.PluginCallInfo;
+import org.apache.sling.scripting.sightly.impl.plugin.PluginInvoke;
+import org.apache.sling.scripting.sightly.impl.compiler.ris.command.Conditional;
+import org.apache.sling.scripting.sightly.impl.compiler.ris.command.OutText;
+import org.apache.sling.scripting.sightly.impl.compiler.ris.command.OutVariable;
+import org.apache.sling.scripting.sightly.impl.compiler.ris.command.VariableBinding;
+import org.apache.sling.scripting.sightly.impl.compiler.util.SymbolGenerator;
+import org.apache.sling.scripting.sightly.impl.compiler.util.stream.PushStream;
+import org.apache.sling.scripting.sightly.impl.html.MarkupUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation for the markup handler
+ */
+public class MarkupHandler {
+
+    private static final Logger log = LoggerFactory.getLogger(MarkupHandler.class);
+
+    private final PushStream stream;
+    private final SymbolGenerator symbolGenerator = new SymbolGenerator();
+    private final ExpressionParser expressionParser = new ExpressionParser();
+    private final Map<String, Plugin> pluginRegistry;
+    private final CompilerContext compilerContext;
+    private final ExpressionWrapper expressionWrapper;
+
+    private final Stack<ElementContext> elementStack = new Stack<ElementContext>();
+
+    public MarkupHandler(PushStream stream, Map<String, Plugin> pluginRegistry, Collection<Filter> filters) {
+        this.stream = stream;
+        this.pluginRegistry = pluginRegistry;
+        this.expressionWrapper = new ExpressionWrapper(filters);
+        this.compilerContext = new CompilerContext(symbolGenerator, expressionWrapper);
+    }
+
+    public void onOpenTagStart(String markup, String tagName) {
+        ElementContext context = new ElementContext(tagName, markup);
+        elementStack.push(context);
+    }
+
+    public void onAttribute(String name, String value) {
+        ElementContext context = elementStack.peek();
+        if (Syntax.isPluginAttribute(name)) {
+            handlePlugin(name, StringUtils.defaultString(value, ""), context);
+        } else {
+            context.addAttribute(name, value);
+        }
+    }
+
+    public void onOpenTagEnd(String markup) {
+        ElementContext context = elementStack.peek();
+        PluginInvoke invoke = context.pluginInvoke();
+        invoke.beforeElement(stream, context.getTagName());
+        invoke.beforeTagOpen(stream);
+        out(context.getOpenTagStartMarkup());
+        invoke.beforeAttributes(stream);
+        traverseAttributes(context, invoke);
+        invoke.afterAttributes(stream);
+        out(markup);
+        invoke.afterTagOpen(stream);
+        invoke.beforeChildren(stream);
+    }
+
+    private void traverseAttributes(ElementContext context, PluginInvoke invoke) {
+        for (Map.Entry<String, Object> attribute : context.getAttributes()) {
+            String attrName = attribute.getKey();
+            Object contentObj = attribute.getValue();
+            if (contentObj == null || contentObj instanceof String) {
+                String content = (String) contentObj;
+                emitAttribute(attrName, content, invoke);
+            } else if (contentObj instanceof Map.Entry) {
+                Map.Entry entry = (Map.Entry) contentObj;
+                PluginCallInfo info = (PluginCallInfo) entry.getKey();
+                Expression expression = (Expression) entry.getValue();
+                invoke.onPluginCall(stream, info, expression);
+            }
+        }
+    }
+
+    private void emitAttribute(String name, String content, PluginInvoke invoke) {
+        invoke.beforeAttribute(stream, name);
+        if (content == null) {
+            emitSimpleTextAttribute(name, null, invoke);
+        } else {
+            Interpolation interpolation = expressionParser.parseInterpolation(content);
+            String text = tryAsSimpleText(interpolation);
+            if (text != null) {
+                emitSimpleTextAttribute(name, text, invoke);
+            } else {
+                emitExpressionAttribute(name, interpolation, invoke);
+            }
+        }
+        invoke.afterAttribute(stream, name);
+    }
+
+    private void emitSimpleTextAttribute(String name, String textValue, PluginInvoke invoke) {
+        emitAttributeStart(name);
+        invoke.beforeAttributeValue(stream, name, new StringConstant(textValue));
+        if (textValue != null) {
+            emitAttributeValueStart();
+            textValue = escapeQuotes(textValue);
+            out(textValue);
+            emitAttributeEnd();
+        }
+        invoke.afterAttributeValue(stream, name);
+    }
+
+    private String escapeQuotes(String textValue) {
+        return textValue.replace("\"", "&quot;");
+    }
+
+    private void emitExpressionAttribute(String name, Interpolation interpolation, PluginInvoke invoke) {
+        interpolation = attributeChecked(name, interpolation);
+        if (interpolation.size() == 1) {
+            emitSingleFragment(name, interpolation, invoke);
+        } else {
+            emitMultipleFragment(name, interpolation, invoke);
+        }
+    }
+
+    private void emitMultipleFragment(String name, Interpolation interpolation, PluginInvoke invoke) {
+        // Simplified algorithm for attribute output, which works when the interpolation is not of size 1. In this
+        // case we are certain that the attribute value cannot be the boolean value true, so we can skip this test
+        // altogether
+        Expression expression = expressionWrapper.transform(interpolation, getAttributeMarkupContext(name));
+        String attrContent = symbolGenerator.next("attrContent");
+        String shouldDisplayAttr = symbolGenerator.next("shouldDisplayAttr");
+        stream.emit(new VariableBinding.Start(attrContent, expression.getRoot()));
+        stream.emit(
+                new VariableBinding.Start(
+                        shouldDisplayAttr,
+                        new BinaryOperation(
+                                BinaryOperator.OR,
+                                new Identifier(attrContent),
+                                new BinaryOperation(BinaryOperator.EQ, new StringConstant("false"), new Identifier(attrContent))
+                        )
+                )
+        );
+        stream.emit(new Conditional.Start(shouldDisplayAttr, true));
+        emitAttributeStart(name);
+        invoke.beforeAttributeValue(stream, name, expression.getRoot());
+        emitAttributeValueStart();
+        stream.emit(new OutVariable(attrContent));
+        emitAttributeEnd();
+        invoke.afterAttributeValue(stream, name);
+        stream.emit(Conditional.END);
+        stream.emit(VariableBinding.END);
+        stream.emit(VariableBinding.END);
+    }
+
+    private void emitSingleFragment(String name, Interpolation interpolation, PluginInvoke invoke) {
+        Expression valueExpression = expressionWrapper.transform(interpolation, null); //raw expression
+        String attrValue = symbolGenerator.next("attrValue"); //holds the raw attribute value
+        String attrContent = symbolGenerator.next("attrContent"); //holds the escaped attribute value
+        String isTrueVar = symbolGenerator.next("isTrueAttr"); // holds the comparison (attrValue == true)
+        String shouldDisplayAttr = symbolGenerator.next("shouldDisplayAttr");
+        MarkupContext markupContext = getAttributeMarkupContext(name);
+        Expression contentExpression = valueExpression.withNode(new Identifier(attrValue));
+        ExpressionNode node = valueExpression.getRoot();
+        stream.emit(new VariableBinding.Start(attrValue, node)); //attrContent = <expr>
+        stream.emit(new VariableBinding.Start(attrContent, expressionWrapper.adjustToContext(contentExpression, markupContext).getRoot()));
+        stream.emit(
+                new VariableBinding.Start(
+                        shouldDisplayAttr,
+                        new BinaryOperation(
+                                BinaryOperator.OR,
+                                new Identifier(attrContent),
+                                new BinaryOperation(BinaryOperator.EQ, new StringConstant("false"), new Identifier(attrValue))
+                        )
+                )
+        );
+        stream.emit(new Conditional.Start(shouldDisplayAttr, true)); // if (attrContent)
+        emitAttributeStart(name);   //write("attrName");
+        invoke.beforeAttributeValue(stream, name, node);
+        stream.emit(new VariableBinding.Start(isTrueVar, //isTrueAttr = (attrValue == true)
+                new BinaryOperation(BinaryOperator.EQ, new Identifier(attrValue), BooleanConstant.TRUE)));
+        stream.emit(new Conditional.Start(isTrueVar, false)); //if (!isTrueAttr)
+        emitAttributeValueStart(); // write("='");
+        stream.emit(new OutVariable(attrContent)); //write(attrContent)
+        emitAttributeEnd(); //write("'");
+        stream.emit(Conditional.END); //end if isTrueAttr
+        stream.emit(VariableBinding.END); //end scope for isTrueAttr
+        invoke.afterAttributeValue(stream, name);
+        stream.emit(Conditional.END); //end if attrContent
+        stream.emit(VariableBinding.END);
+        stream.emit(VariableBinding.END); //end scope for attrContent
+        stream.emit(VariableBinding.END); //end scope for attrValue
+    }
+
+
+    private void emitAttributeStart(String name) {
+        out(" " + name);
+    }
+
+    private void emitAttributeValueStart() {
+        out("=\"");
+    }
+
+    private void emitAttributeEnd() {
+        out("\"");
+    }
+
+
+    public void onCloseTag(String markup) {
+        ElementContext context = elementStack.pop();
+        PluginInvoke invoke = context.pluginInvoke();
+        invoke.afterChildren(stream);
+        boolean selfClosingTag = StringUtils.isEmpty(markup);
+        invoke.beforeTagClose(stream, selfClosingTag);
+        out(markup);
+        invoke.afterTagClose(stream, selfClosingTag);
+        invoke.afterElement(stream);
+    }
+
+
+    public void onText(String text) {
+        String tag = currentElementTag();
+        boolean explicitContextRequired = isExplicitContextRequired(tag);
+        MarkupContext markupContext = (explicitContextRequired) ? null : MarkupContext.TEXT;
+        outText(text, markupContext);
+    }
+
+
+    public void onComment(String markup) {
+        if (!Syntax.isSightlyComment(markup)) {
+            outText(markup, MarkupContext.COMMENT);
+        }
+    }
+
+
+    public void onDataNode(String markup) {
+        out(markup);
+    }
+
+
+    public void onDocType(String markup) {
+        out(markup);
+    }
+
+
+    public void onDocumentFinished() {
+        this.stream.signalDone();
+    }
+
+    private void outText(String content, MarkupContext context) {
+        Interpolation interpolation = expressionParser.parseInterpolation(content);
+        if (context == null) {
+            interpolation = requireContext(interpolation);
+        }
+        String text = tryAsSimpleText(interpolation);
+        if (text != null) {
+            out(text);
+        } else {
+            outExprNode(expressionWrapper.transform(interpolation, context).getRoot());
+        }
+    }
+
+    private Interpolation requireContext(Interpolation interpolation) {
+        Interpolation result = new Interpolation();
+        for (Fragment fragment : interpolation.getFragments()) {
+            Fragment addedFragment;
+            if (fragment.isString()) {
+                addedFragment = fragment;
+            } else {
+                if (fragment.getExpression().containsOption(Syntax.CONTEXT_OPTION)) {
+                    addedFragment = fragment;
+                } else {
+                    String currentTag = currentElementTag();
+                    log.warn("Element {} requires that all expressions have an explicit context specified. Expression will be " +
+                            "replaced by the empty string", currentTag);
+                    addedFragment = new Fragment.Expr(new Expression(StringConstant.EMPTY));
+                }
+            }
+            result.addFragment(addedFragment);
+        }
+        return result;
+    }
+
+    private Interpolation attributeChecked(String attributeName, Interpolation interpolation) {
+        if (!MarkupUtils.isSensitiveAttribute(attributeName)) {
+            return interpolation;
+        }
+        Interpolation newInterpolation = new Interpolation();
+        for (Fragment fragment : interpolation.getFragments()) {
+            Fragment addedFragment = fragment;
+            if (fragment.isExpression()) {
+                Expression expression = fragment.getExpression();
+                if (!expression.containsOption(Syntax.CONTEXT_OPTION)) {
+                    log.warn("All expressions within the value of attribute {} need to have an explicit context option. The expression will be erased.",
+                            attributeName);
+                    addedFragment = new Fragment.Text("");
+                }
+            }
+            newInterpolation.addFragment(addedFragment);
+        }
+        return newInterpolation;
+    }
+
+
+    private void outExprNode(ExpressionNode node) {
+        String variable = symbolGenerator.next();
+        stream.emit(new VariableBinding.Start(variable, node));
+        stream.emit(new OutVariable(variable));
+        stream.emit(VariableBinding.END);
+    }
+
+
+    private String tryAsSimpleText(Interpolation interpolation) {
+        if (interpolation.size() == 1) {
+            Fragment fragment = interpolation.getFragment(0);
+            if (fragment.isString()) {
+                return fragment.getText();
+            }
+        } else if (interpolation.size() == 0) {
+            return "";
+        }
+        return null;
+    }
+
+    private void out(String text) {
+        stream.emit(new OutText(text));
+    }
+
+    private void handlePlugin(String name, String value, ElementContext context) {
+        PluginCallInfo callInfo = Syntax.parsePluginAttribute(name);
+        if (callInfo != null) {
+            Plugin plugin = obtainPlugin(callInfo.getName());
+            Expression expr = expressionWrapper.transform(
+                    expressionParser.parseInterpolation(value), null);
+            PluginInvoke invoke = plugin.invoke(expr, callInfo, compilerContext);
+            context.addPlugin(invoke, plugin.priority());
+            context.addPluginCall(name, callInfo, expr);
+        }
+    }
+
+    private Plugin obtainPlugin(String name) {
+        Plugin plugin = pluginRegistry.get(name);
+        if (plugin == null) {
+            throw new UnsupportedOperationException(String.format("Plugin %s does not exist", name));
+        }
+        return plugin;
+    }
+
+    private MarkupContext getAttributeMarkupContext(String attributeName) {
+        if ("src".equalsIgnoreCase(attributeName) || "href".equalsIgnoreCase(attributeName)) {
+            return MarkupContext.URI;
+        }
+        return MarkupContext.ATTRIBUTE;
+    }
+
+    private String currentElementTag() {
+        if (elementStack.isEmpty()) {
+            return null;
+        }
+        ElementContext current = elementStack.peek();
+        return current.getTagName();
+    }
+
+    private boolean isExplicitContextRequired(String parentElementName) {
+        return parentElementName != null &&
+                ("script".equals(parentElementName) || "style".equals(parentElementName));
+    }
+}

Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/TagTokenizer.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/TagTokenizer.java?rev=1642281&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/TagTokenizer.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/TagTokenizer.java Fri Nov 28 10:18:01 2014
@@ -0,0 +1,514 @@
+/*******************************************************************************
+ * 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;
+
+import java.io.CharArrayWriter;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tokenizes a snippet of characters into a structured tag/attribute name list.
+ */
+class TagTokenizer {
+    /** Tag name buffer */
+    private final CharArrayWriter tagName = new CharArrayWriter(30);
+
+    /** Attribute name buffer */
+    private final CharArrayWriter attName = new CharArrayWriter(30);
+
+    /** Attribute value buffer */
+    private final CharArrayWriter attValue = new CharArrayWriter(30);
+
+    /** Internal property list */
+    private final AttributeListImpl attributes = new AttributeListImpl();
+
+    /** Parse state constant */
+    private final static int START = 0;
+
+    /** Parse state constant */
+    private final static int TAG = START + 1;
+
+    /** Parse state constant */
+    private final static int NAME = TAG + 1;
+
+    /** Parse state constant */
+    private final static int INSIDE = NAME + 1;
+
+    /** Parse state constant */
+    private final static int ATTNAME = INSIDE + 1;
+
+    /** Parse state constant */
+    private final static int EQUAL = ATTNAME + 1;
+
+    /** Parse state constant */
+    private final static int ATTVALUE = EQUAL + 1;
+
+    /** Parse state constant */
+    private final static int STRING = ATTVALUE + 1;
+
+    /** Parse state constant */
+    private final static int ENDSLASH = STRING + 1;
+
+    /** Parse state constant */
+    private final static int END = ENDSLASH + 1;
+
+    /** Parse state constant */
+    private final static int BETWEEN_ATTNAME = END + 1;
+
+    /** Quote character */
+    private char quoteChar = '"';
+
+    /** Flag indicating whether the tag scanned is an end tag */
+    private boolean endTag;
+
+    /** Flag indicating whether an ending slash was parsed */
+    private boolean endSlash;
+
+    /** temporary flag indicating if attribute has a value */
+    private boolean hasAttributeValue;
+
+    /**
+     * Scan characters passed to this parser
+     */
+    public void tokenize(char[] buf, int off, int len) {
+        reset();
+
+        int parseState = START;
+
+        for (int i = 0; i < len; i++) {
+            char c = buf[off + i];
+
+            switch (parseState) {
+                case START:
+                    if (c == '<') {
+                        parseState = TAG;
+                    }
+                    break;
+                case TAG:
+                    if (c == '/') {
+                        endTag = true;
+                        parseState = NAME;
+                    } else if (c == '"' || c == '\'') {
+                        quoteChar = c;
+                        parseState = STRING;
+                    } else if (Character.isWhitespace(c)) {
+                        parseState = INSIDE;
+                    } else {
+                        tagName.write(c);
+                        parseState = NAME;
+                    }
+                    break;
+                case NAME:
+                    if (Character.isWhitespace(c)) {
+                        parseState = INSIDE;
+                    } else if (c == '"' || c == '\'') {
+                        quoteChar = c;
+                        parseState = STRING;
+                    } else if (c == '>') {
+                        parseState = END;
+                    } else if (c == '/') {
+                        parseState = ENDSLASH;
+                    } else {
+                        tagName.write(c);
+                    }
+                    break;
+                case INSIDE:
+                    if (c == '>') {
+                        attributeEnded();
+                        parseState = END;
+                    } else if (c == '/') {
+                        attributeEnded();
+                        parseState = ENDSLASH;
+                    } else if (c == '"' || c == '\'') {
+                        attributeValueStarted();
+                        quoteChar = c;
+                        parseState = STRING;
+                    } else if (c == '=') {
+                        parseState = EQUAL;
+                    } else if (!Character.isWhitespace(c)) {
+                        attName.write(c);
+                        parseState = ATTNAME;
+                    }
+                    break;
+                case ATTNAME:
+                    if (c == '>') {
+                        attributeEnded();
+                        parseState = END;
+                    } else if (c == '/') {
+                        attributeEnded();
+                        parseState = ENDSLASH;
+                    } else if (c == '=') {
+                        parseState = EQUAL;
+                    } else if (c == '"' || c == '\'') {
+                        quoteChar = c;
+                        parseState = STRING;
+                    } else if (Character.isWhitespace(c)) {
+                        parseState = BETWEEN_ATTNAME;
+                    } else {
+                        attName.write(c);
+                    }
+                    break;
+                case BETWEEN_ATTNAME:
+                    if (c == '>') {
+                        attributeEnded();
+                        parseState = END;
+                    } else if (c == '/') {
+                        attributeEnded();
+                        parseState = ENDSLASH;
+                    } else if (c == '"' || c == '\'') {
+                        attributeValueStarted();
+                        quoteChar = c;
+                        parseState = STRING;
+                    } else if (c == '=') {
+                        parseState = EQUAL;
+                    } else if (!Character.isWhitespace(c)) {
+                        attributeEnded();
+                        attName.write(c);
+                        parseState = ATTNAME;
+                    }
+                    break;
+                case EQUAL:
+                    if (c == '>') {
+                        attributeEnded();
+                        parseState = END;
+                    } else if (c == '"' || c == '\'') {
+                        attributeValueStarted();
+                        quoteChar = c;
+                        parseState = STRING;
+                    } else if (!Character.isWhitespace(c)) {
+                        attributeValueStarted();
+                        attValue.write(c);
+                        parseState = ATTVALUE;
+                    }
+                    break;
+                case ATTVALUE:
+                    if (Character.isWhitespace(c)) {
+                        attributeEnded();
+                        parseState = INSIDE;
+                    } else if (c == '"' || c == '\'') {
+                        attributeEnded();
+                        quoteChar = c;
+                        parseState = STRING;
+                    } else if (c == '>') {
+                        attributeEnded();
+                        parseState = END;
+                    } else {
+                        attValue.write(c);
+                    }
+                    break;
+                case STRING:
+                    if (c == quoteChar) {
+                        attributeEnded();
+                        parseState = INSIDE;
+                    } else {
+                        attValue.write(c);
+                    }
+                    break;
+                case ENDSLASH:
+                    if (c == '>') {
+                        endSlash = true;
+                        parseState = END;
+                    } else if (c == '"' || c == '\'') {
+                        quoteChar = c;
+                        parseState = STRING;
+                    } else if (c != '/' && !Character.isWhitespace(c)) {
+                        attName.write(c);
+                        parseState = ATTNAME;
+                    } else {
+                        parseState = INSIDE;
+                    }
+                    break;
+                case END:
+                    break;
+
+            }
+        }
+    }
+
+    /**
+     * Return a flag indicating whether the tag scanned was an end tag
+     * @return <code>true</code> if it was an end tag, otherwise
+     *         <code>false</code>
+     */
+    public boolean endTag() {
+        return endTag;
+    }
+
+    /**
+     * Return a flag indicating whether an ending slash was scanned
+     * @return <code>true</code> if an ending slash was scanned, otherwise
+     *         <code>false</code>
+     */
+    public boolean endSlash() {
+        return endSlash;
+    }
+
+    /**
+     * Return the tagname scanned
+     * @return tag name
+     */
+    public String tagName() {
+        return tagName.toString();
+    }
+
+    /**
+     * Return the list of attributes scanned
+     * @return list of attributes
+     */
+    public AttributeList attributes() {
+        return attributes;
+    }
+
+    /**
+     * Reset the internal state of the tokenizer
+     */
+    private void reset() {
+        tagName.reset();
+        attributes.reset();
+        endTag = false;
+        endSlash = false;
+    }
+
+    /**
+     * Invoked when an attribute ends
+     */
+    private void attributeEnded() {
+        if (attName.size() > 0) {
+            if (hasAttributeValue) {
+                attributes.addAttribute(attName.toString(), attValue.toString(),
+                        quoteChar);
+            } else {
+                attributes.addAttribute(attName.toString(), quoteChar);
+
+            }
+            attName.reset();
+            attValue.reset();
+            hasAttributeValue = false;
+        }
+    }
+
+    /**
+     * Invoked when an attribute value starts
+     */
+    private void attributeValueStarted() {
+        hasAttributeValue = true;
+    }
+
+    /**
+     * Retransfers the tokenized tag data into html again
+     * @return the reassembled html string
+     */
+    public String toHtmlString() {
+        StringBuffer sb = new StringBuffer();
+        if (endTag) {
+            sb.append("</" + tagName());
+        } else {
+            sb.append("<" + tagName());
+            Iterator<String> attNames = attributes().attributeNames();
+            while (attNames.hasNext()) {
+                String attName = attNames.next();
+                String attValue = attributes().getQuotedValue(attName);
+
+                sb.append(" ");
+                sb.append(attName);
+                if (attValue != null) {
+                    sb.append('=');
+                    sb.append(attValue);
+                }
+            }
+            if (endSlash) {
+                sb.append(" /");
+            }
+        }
+        sb.append(">");
+        return sb.toString();
+    }
+}
+
+/**
+ * Internal implementation of an <code>AttributeList</code>
+ */
+class AttributeListImpl implements AttributeList {
+
+    /**
+     * Internal Value class
+     */
+    static class Value {
+
+        /**
+         * Create a new <code>Value</code> instance
+         */
+        public Value(char quoteChar, String value) {
+            this.quoteChar = quoteChar;
+            this.value = value;
+        }
+
+        /** Quote character */
+        public final char quoteChar;
+
+        /** Value itself */
+        public final String value;
+
+        /** String representation */
+        private String stringRep;
+
+        /**
+         * @see Object#toString()
+         */
+        @Override
+        public String toString() {
+            if (stringRep == null) {
+                stringRep = quoteChar + value + quoteChar;
+            }
+            return stringRep;
+        }
+    }
+
+    /** Attribute/Value pair map with case insensitives names */
+    private final Map<String, Value> attributes = new LinkedHashMap<String, Value>();
+
+    /** Attribute names, case sensitive */
+    private final Set<String> attributeNames = new LinkedHashSet<String>();
+
+    /** Flag indicating whether this object was modified */
+    private boolean modified;
+
+    /**
+     * Add an attribute/value pair to this attribute list
+     */
+    public void addAttribute(String name, String value, char quoteChar) {
+        attributes.put(name.toUpperCase(), new Value(quoteChar, value));
+        attributeNames.add(name);
+    }
+
+    /**
+     * Add an attribute/value pair to this attribute list
+     */
+    public void addAttribute(String name, char quoteChar) {
+        attributes.put(name.toUpperCase(), null);
+        attributeNames.add(name);
+    }
+
+    /**
+     * Empty this attribute list
+     */
+    public void reset() {
+        attributes.clear();
+        attributeNames.clear();
+        modified = false;
+    }
+
+    /**
+     * @see AttributeList#attributeCount
+     */
+    public int attributeCount() {
+        return attributes.size();
+    }
+
+    /**
+     * @see AttributeList#attributeNames
+     */
+    public Iterator<String> attributeNames() {
+        return attributeNames.iterator();
+    }
+
+    /**
+     * @see AttributeList#containsAttribute(String)
+     */
+    public boolean containsAttribute(String name) {
+        return attributes.containsKey(name.toUpperCase());
+    }
+
+    /**
+     * @see AttributeList#getValue(String)
+     */
+    public String getValue(String name) {
+        Value value = getValueEx(name);
+        if (value != null) {
+            return value.value;
+        }
+        return null;
+    }
+
+    /**
+     * @see AttributeList#getQuoteChar(java.lang.String)
+     */
+    public char getQuoteChar(String name) {
+        Value value = getValueEx(name);
+        if (value != null) {
+            return value.quoteChar;
+        }
+        return 0;
+    }
+
+    /**
+     * @see AttributeList#getQuotedValue(String)
+     */
+    public String getQuotedValue(String name) {
+        Value value = getValueEx(name);
+        if (value != null) {
+            return value.toString();
+        }
+        return null;
+    }
+
+    /**
+     * @see AttributeList#setValue(String, String)
+     */
+    public void setValue(String name, String value) {
+        if (value == null) {
+            removeValue(name);
+        } else {
+            Value old = getValueEx(name);
+            if (old == null) {
+                addAttribute(name, value, '"');
+                modified = true;
+            } else if (!old.value.equals(value)) {
+                addAttribute(name, value, old.quoteChar);
+                modified = true;
+            }
+        }
+    }
+
+    /**
+     * @see AttributeList#removeValue(String)
+     */
+    public void removeValue(String name) {
+        attributeNames.remove(name);
+        attributes.remove(name.toUpperCase());
+        modified = true;
+    }
+
+    /**
+     * @see AttributeList#isModified
+     */
+    public boolean isModified() {
+        return modified;
+    }
+
+    /**
+     * Return internal value structure
+     */
+    protected Value getValueEx(String name) {
+        return attributes.get(name.toUpperCase());
+    }
+}
\ No newline at end of file

Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/TagTokenizer.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/TreeTraverser.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/TreeTraverser.java?rev=1642281&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/TreeTraverser.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/TreeTraverser.java Fri Nov 28 10:18:01 2014
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * 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;
+
+import org.apache.sling.scripting.sightly.impl.html.dom.template.Template;
+import org.apache.sling.scripting.sightly.impl.html.dom.template.TemplateAttribute;
+import org.apache.sling.scripting.sightly.impl.html.dom.template.TemplateCommentNode;
+import org.apache.sling.scripting.sightly.impl.html.dom.template.TemplateElementNode;
+import org.apache.sling.scripting.sightly.impl.html.dom.template.TemplateNode;
+import org.apache.sling.scripting.sightly.impl.html.dom.template.TemplateTextNode;
+
+public class TreeTraverser {
+
+    private final MarkupHandler handler;
+
+    public TreeTraverser(MarkupHandler handler) {
+        this.handler = handler;
+    }
+
+    public void traverse(Template template) {
+        traverseNode(template);
+        this.handler.onDocumentFinished();
+    }
+
+    private void traverseNode(TemplateNode node) {
+        if (node instanceof TemplateElementNode) {
+            traverseElement((TemplateElementNode) node);
+        } else if (node instanceof TemplateTextNode) {
+            traverseText((TemplateTextNode) node);
+        } else if (node instanceof TemplateCommentNode) {
+            traverseComment((TemplateCommentNode) node);
+        } else {
+            throw new IllegalArgumentException("Unknown node type");
+        }
+    }
+
+    private void traverseElement(TemplateElementNode elem) {
+        if ("ROOT".equalsIgnoreCase(elem.getName())) {
+            traverseChildren(elem);
+            return;
+        }
+        String tagName = elem.getName();
+
+        if (elem.isHasStartElement()) {
+            handler.onOpenTagStart("<" + tagName, tagName);
+            for (TemplateAttribute attribute : elem.getAttributes()) {
+                handler.onAttribute(attribute.getName(), attribute.getValue());
+            }
+            if (elem.isHasEndSlash()) {
+                handler.onOpenTagEnd("/>");
+            } else {
+                handler.onOpenTagEnd(">");
+            }
+        } else {
+            handler.onOpenTagStart("", tagName);
+            handler.onOpenTagEnd("");
+        }
+
+        traverseChildren(elem);
+
+        if (elem.isHasEndElement()) {
+            handler.onCloseTag("</" + elem.getName() + ">");
+        } else {
+            handler.onCloseTag("");
+        }
+    }
+
+    private void traverseText(TemplateTextNode textNode) {
+        handler.onText(textNode.getText());
+    }
+
+    private void traverseComment(TemplateCommentNode comment) {
+        handler.onComment(comment.getText());
+    }
+
+    private void traverseChildren(TemplateElementNode elem) {
+        for (TemplateNode node : elem.getChildren()) {
+            traverseNode(node);
+        }
+    }
+
+}

Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/TreeTraverser.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/Template.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/Template.java?rev=1642281&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/Template.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/Template.java Fri Nov 28 10:18:01 2014
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * 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;
+
+
+public class Template extends TemplateElementNode {
+
+    public Template() {
+        super("ROOT", false, null);
+    }
+}

Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/Template.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateAttribute.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateAttribute.java?rev=1642281&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateAttribute.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateAttribute.java Fri Nov 28 10:18:01 2014
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * 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;
+
+public class TemplateAttribute {
+
+    private final String name;
+
+    private final String value;
+
+    public TemplateAttribute(final String name, final String value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getValue() {
+        return this.value;
+    }
+
+    public boolean hasValue() {
+        return value != null;
+    }
+}

Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateAttribute.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateCommentNode.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateCommentNode.java?rev=1642281&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateCommentNode.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateCommentNode.java Fri Nov 28 10:18:01 2014
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * 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;
+
+public class TemplateCommentNode extends TemplateNode {
+
+    private final String text;
+
+    public TemplateCommentNode(final String t) {
+        this.text = t;
+    }
+
+    public String getText() {
+        return this.text;
+    }
+}

Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateCommentNode.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateElementNode.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateElementNode.java?rev=1642281&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateElementNode.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateElementNode.java Fri Nov 28 10:18:01 2014
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * 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.util.ArrayList;
+import java.util.List;
+
+public class TemplateElementNode extends TemplateNode {
+
+    private final String name;
+
+    private final boolean hasEndSlash;
+
+    private boolean hasEndElement = false;
+
+    private boolean hasStartElement = false;
+
+    private final List<TemplateAttribute> attributes;
+
+    private final List<TemplateNode> children = new ArrayList<TemplateNode>();
+
+    public TemplateElementNode(final String name,
+                               final boolean hasEndSlash,
+                               final List<TemplateAttribute> attributes) {
+        this.name = name;
+        this.hasEndSlash = hasEndSlash;
+        this.attributes = attributes;
+    }
+
+    public void setHasStartElement() {
+        this.hasStartElement = true;
+    }
+
+    public void setHasEndElement() {
+        this.hasEndElement = true;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public boolean isHasEndSlash() {
+        return hasEndSlash;
+    }
+
+    public boolean isHasStartElement() {
+        return hasStartElement;
+    }
+
+    public boolean isHasEndElement() {
+        return hasEndElement;
+    }
+
+    public List<TemplateAttribute> getAttributes() {
+        return attributes;
+    }
+
+    public void addChild(final TemplateNode node) {
+        this.children.add(node);
+    }
+
+    public List<TemplateNode> getChildren() {
+        return this.children;
+    }
+}

Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateElementNode.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateNode.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateNode.java?rev=1642281&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateNode.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateNode.java Fri Nov 28 10:18:01 2014
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * 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;
+
+public abstract class TemplateNode {
+
+}

Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateNode.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateParser.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateParser.java?rev=1642281&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateParser.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateParser.java Fri Nov 28 10:18:01 2014
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * 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));
+                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) {
+            Iterator it = this.elementStack.iterator(); // ascending iterator
+            while (it.hasNext()) {
+                TemplateElementNode elem = (TemplateElementNode) it.next();
+                if (name.equals(elem.getName())) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+}

Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateParser.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateTextNode.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateTextNode.java?rev=1642281&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateTextNode.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateTextNode.java Fri Nov 28 10:18:01 2014
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * 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;
+
+public class TemplateTextNode extends TemplateNode {
+
+    private final String text;
+
+    public TemplateTextNode(final String t) {
+        this.text = t;
+    }
+
+    public String getText() {
+        return this.text;
+    }
+}

Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateTextNode.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain