You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by bd...@apache.org on 2014/11/14 15:05:03 UTC

svn commit: r1639641 [10/15] - in /sling/trunk/contrib/scripting/sightly: ./ engine/ engine/src/ engine/src/main/ engine/src/main/antlr4/ engine/src/main/antlr4/org/ engine/src/main/antlr4/org/apache/ engine/src/main/antlr4/org/apache/sling/ engine/src...

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/engine/extension/use/ScriptUseProvider.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/engine/extension/use/ScriptUseProvider.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/engine/extension/use/ScriptUseProvider.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/engine/extension/use/ScriptUseProvider.java Fri Nov 14 14:04:56 2014
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * 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.engine.extension.use;
+
+import javax.script.Bindings;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.api.scripting.SlingScript;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+import org.apache.sling.scripting.sightly.api.ProviderOutcome;
+import org.apache.sling.scripting.sightly.api.UseProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.sling.scripting.sightly.api.ProviderOutcome;
+import org.apache.sling.scripting.sightly.api.RenderContext;
+import org.apache.sling.scripting.sightly.api.SightlyUseException;
+import org.apache.sling.scripting.sightly.api.UseProvider;
+import org.apache.sling.scripting.sightly.api.UseProviderComponent;
+
+/**
+ * Use provider that interprets the identifier as a script path, and runs the respective script using a script engine that matches the
+ * script extension.
+ *
+ * This provider returns a non-failure outcome only if the evaluated script actually returns something. For more details check the
+ * implementation of the {@link SlingScript#eval(SlingBindings)} method for the available script engines from your platform.
+ */
+@Component
+@Service(UseProvider.class)
+@Property(name = UseProviderComponent.PRIORITY, intValue = 15)
+public class ScriptUseProvider extends UseProviderComponent {
+
+    private static final Logger log = LoggerFactory.getLogger(ScriptUseProvider.class);
+
+    @Reference
+    private ResourceResolverFactory rrf = null;
+
+    @Override
+    public ProviderOutcome provide(String scriptName, RenderContext renderContext, Bindings arguments) {
+        Bindings globalBindings = renderContext.getBindings();
+        Bindings bindings = merge(globalBindings, arguments);
+        String extension = scriptExtension(scriptName);
+        if (extension == null) {
+            return ProviderOutcome.failure();
+        }
+        SlingScriptHelper sling = (SlingScriptHelper) bindings.get(SlingBindings.SLING);
+        ResourceResolver adminResolver = null;
+        try {
+            adminResolver = rrf.getAdministrativeResourceResolver(null);
+            if (adminResolver == null) {
+                log.warn("Cannot obtain administrative resource resolver for " + scriptName);
+                return ProviderOutcome.failure();
+            }
+            Resource scriptResource = ScriptEvalUtils.locateScriptResource(adminResolver, sling, scriptName);
+            if (scriptResource == null) {
+                log.debug("Path does not match an existing resource: {}", scriptName);
+                return ProviderOutcome.failure();
+            }
+            return evalScript(scriptResource, bindings);
+        } catch (LoginException e) {
+            throw new SightlyUseException(e);
+        } finally {
+            if (adminResolver != null) {
+                adminResolver.close();
+            }
+        }
+
+    }
+
+    private ProviderOutcome evalScript(Resource scriptResource, Bindings bindings) {
+        SlingScript slingScript = scriptResource.adaptTo(SlingScript.class);
+        if (slingScript == null) {
+            return ProviderOutcome.failure();
+        }
+        SlingBindings slingBindings = new SlingBindings();
+        slingBindings.putAll(bindings);
+        Object scriptEval = slingScript.eval(slingBindings);
+        return ProviderOutcome.notNullOrFailure(scriptEval);
+    }
+
+    private String scriptExtension(String path) {
+        String extension = StringUtils.substringAfterLast(path, ".");
+        if (StringUtils.isEmpty(extension)) {
+            extension = null;
+        }
+        return extension;
+    }
+
+}

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/engine/extension/use/UseRuntimeExtension.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/engine/extension/use/UseRuntimeExtension.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/engine/extension/use/UseRuntimeExtension.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/engine/extension/use/UseRuntimeExtension.java Fri Nov 14 14:04:56 2014
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * 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.engine.extension.use;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.script.Bindings;
+import javax.script.SimpleBindings;
+
+import org.apache.commons.lang.StringUtils;
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.Service;
+
+import org.apache.sling.scripting.sightly.api.RuntimeExtensionComponent;
+import org.apache.sling.scripting.sightly.api.ExtensionInstance;
+import org.apache.sling.scripting.sightly.api.ProviderOutcome;
+import org.apache.sling.scripting.sightly.api.RenderContext;
+import org.apache.sling.scripting.sightly.api.RuntimeExtension;
+import org.apache.sling.scripting.sightly.api.RuntimeExtensionException;
+import org.apache.sling.scripting.sightly.api.SightlyUseException;
+import org.apache.sling.scripting.sightly.api.UseProvider;
+import org.apache.sling.scripting.sightly.plugin.UsePlugin;
+
+/**
+ * Runtime extension for the USE plugin
+ */
+@Component
+@Service(RuntimeExtension.class)
+@Properties(
+        @Property(name = RuntimeExtensionComponent.SCR_PROP_NAME, value = UsePlugin.FUNCTION_NAME)
+)
+@Reference(
+        policy = ReferencePolicy.DYNAMIC,
+        referenceInterface = UseProvider.class,
+        name = "useProvider",
+        cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE
+)
+public class UseRuntimeExtension extends RuntimeExtensionComponent {
+
+    private volatile List<UseProvider> providers = Collections.emptyList();
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public ExtensionInstance provide(final RenderContext renderContext) {
+        return new ExtensionInstance() {
+
+            @Override
+            public Object call(Object... arguments) {
+                if (arguments.length != 2) {
+                    throw new RuntimeExtensionException("Use extension requires two arguments");
+                }
+                String identifier = renderContext.getObjectModel().coerceToString(arguments[0]);
+                if (StringUtils.isEmpty(identifier)) {
+                    return null;
+                }
+                Map<String, Object> useArgumentsMap = renderContext.getObjectModel().coerceToMap(arguments[1]);
+                Bindings useArguments = new SimpleBindings(Collections.unmodifiableMap(useArgumentsMap));
+                for (UseProvider provider : providers) {
+                    ProviderOutcome outcome = provider.provide(identifier, renderContext, useArguments);
+                    if (outcome.isSuccess()) {
+                        return outcome.getResult();
+                    }
+                }
+                throw new SightlyUseException("No use provider could resolve identifier: " + identifier);
+            }
+        };
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    private void bindUseProvider(UseProvider provider) {
+        ArrayList<UseProvider> newProviders = new ArrayList<UseProvider>(providers);
+        newProviders.add(provider);
+        Collections.sort(newProviders);
+        providers = newProviders;
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    private void unbindUseProvider(UseProvider provider) {
+        ArrayList<UseProvider> newProviders = new ArrayList<UseProvider>(providers);
+        newProviders.remove(provider);
+        Collections.sort(newProviders);
+        providers = newProviders;
+    }
+}

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/engine/runtime/UnitLocatorImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/engine/runtime/UnitLocatorImpl.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/engine/runtime/UnitLocatorImpl.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/engine/runtime/UnitLocatorImpl.java Fri Nov 14 14:04:56 2014
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * 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.engine.runtime;
+
+import javax.script.Bindings;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.scripting.sightly.api.RenderUnit;
+import org.apache.sling.scripting.sightly.api.ResourceResolution;
+import org.apache.sling.scripting.sightly.api.UnitLocator;
+import org.apache.sling.scripting.sightly.engine.UnitLoader;
+
+/**
+ * Implementation for unit locator
+ */
+public class UnitLocatorImpl implements UnitLocator {
+
+    private final UnitLoader unitLoader;
+    private final ResourceResolver resolver;
+    private final Bindings bindings;
+    private final Resource currentScriptResource;
+
+    public UnitLocatorImpl(UnitLoader unitLoader, ResourceResolver resourceResolver,
+                           Bindings bindings, Resource currentScriptResource) {
+        this.unitLoader = unitLoader;
+        this.resolver = resourceResolver;
+        this.bindings = bindings;
+        this.currentScriptResource = currentScriptResource;
+    }
+
+    @Override
+    public RenderUnit locate(String path) {
+        Resource resource = locateResource(path);
+        if (resource == null) {
+            return null;
+        }
+        return unitLoader.createUnit(resource, bindings);
+    }
+
+    private Resource locateResource(String script) {
+        SlingHttpServletRequest request = (SlingHttpServletRequest) bindings.get(SlingBindings.REQUEST);
+        Resource resource = ResourceResolution.resolveComponentForRequest(resolver, request);
+        if (resource != null) {
+            resource = ResourceResolution.resolveComponentRelative(resolver, resource, script);
+        } else {
+            resource = ResourceResolution.resolveComponentRelative(resolver, currentScriptResource, script);
+        }
+        return resource;
+    }
+
+}

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/FilterComponent.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/FilterComponent.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/FilterComponent.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/FilterComponent.java Fri Nov 14 14:04:56 2014
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * 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.filter;
+
+import java.util.Dictionary;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.scripting.sightly.compiler.api.Filter;
+import org.osgi.service.component.ComponentContext;
+
+import org.apache.sling.scripting.sightly.compiler.api.Filter;
+
+/**
+ * Filters implemented as components.
+ */
+public abstract class FilterComponent implements Filter {
+
+    public static final String PRIORITY = "org.apache.sling.scripting.sightly.filter.priority";
+    public static final int DEFAULT_PRIORITY = 100;
+
+    private int priority = DEFAULT_PRIORITY;
+
+    @Override
+    public int priority() {
+        return priority;
+    }
+
+    @Override
+    public int compareTo(Filter o) {
+        if (this.priority < o.priority()) {
+            return -1;
+        } else if (this.priority == o.priority()) {
+            return  0;
+        }
+        return 1;
+    }
+
+    @Activate
+    protected void activate(ComponentContext componentContext) {
+        Dictionary properties = componentContext.getProperties();
+        priority = PropertiesUtil.toInteger(properties.get(PRIORITY), DEFAULT_PRIORITY);
+    }
+}

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/FormatFilter.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/FormatFilter.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/FormatFilter.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/FormatFilter.java Fri Nov 14 14:04:56 2014
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * 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.filter;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+
+import org.apache.sling.scripting.sightly.compiler.api.Filter;
+import org.apache.sling.scripting.sightly.compiler.api.expression.Expression;
+import org.apache.sling.scripting.sightly.compiler.api.expression.ExpressionNode;
+import org.apache.sling.scripting.sightly.compiler.api.expression.node.RuntimeCall;
+import org.apache.sling.scripting.sightly.api.ExtensionInstance;
+import org.apache.sling.scripting.sightly.api.RenderContext;
+import org.apache.sling.scripting.sightly.api.RuntimeExtension;
+import org.apache.sling.scripting.sightly.api.RuntimeExtensionException;
+import org.apache.sling.scripting.sightly.common.Dynamic;
+import org.apache.sling.scripting.sightly.api.ExtensionInstance;
+import org.apache.sling.scripting.sightly.common.Dynamic;
+import org.apache.sling.scripting.sightly.compiler.api.Filter;
+import org.apache.sling.scripting.sightly.compiler.api.expression.Expression;
+
+/**
+ * Implementation for the format filter & runtime support.
+ */
+@Component
+@Service({Filter.class, RuntimeExtension.class})
+public class FormatFilter extends FilterComponent implements RuntimeExtension {
+
+    public static final String FORMAT_OPTION = "format";
+    public static final String FORMAT_FUNCTION = "format";
+
+    private static final Pattern PLACEHOLDER_REGEX = Pattern.compile("\\{\\d}");
+
+    @Override
+    public Expression apply(Expression expression) {
+        //todo: if the expression is a string constant, we can produce the transformation at
+        //compile time, with no need of a runtime function
+        if (!expression.containsOption(FORMAT_OPTION)) {
+            return expression;
+        }
+        ExpressionNode argNode = expression.getOption(FORMAT_OPTION);
+        ExpressionNode formattedNode = new RuntimeCall(FORMAT_FUNCTION, expression.getRoot(), argNode);
+        return expression.withNode(formattedNode).removeOptions(FORMAT_OPTION);
+    }
+
+    @Override
+    public String name() {
+        return FORMAT_FUNCTION;
+    }
+
+    @Override
+    public ExtensionInstance provide(RenderContext renderContext) {
+        final Dynamic dynamic = new Dynamic(renderContext.getObjectModel());
+
+        return new ExtensionInstance() {
+            @Override
+            public Object call(Object... arguments) {
+                if (arguments.length != 2) {
+                    throw new RuntimeExtensionException("Format function must be called with two arguments");
+                }
+                String source = dynamic.coerceToString(arguments[0]);
+                Object[] params = decodeParams(arguments[1]);
+                return replace(source, params);
+            }
+
+            private Object[] decodeParams(Object paramObj) {
+                if (dynamic.isCollection(paramObj)) {
+                    return dynamic.coerceToCollection(paramObj).toArray();
+                }
+                return new Object[] {paramObj};
+            }
+
+            private String replace(String source, Object[] params) {
+                Matcher matcher = PLACEHOLDER_REGEX.matcher(source);
+                StringBuilder builder = new StringBuilder();
+                int lastPos = 0;
+                boolean matched = true;
+                while (matched) {
+                    matched = matcher.find();
+                    if (matched) {
+                        int paramIndex = placeholderIndex(matcher.group());
+                        String replacement = param(params, paramIndex);
+                        int matchStart = matcher.start();
+                        int matchEnd = matcher.end();
+                        builder.append(source, lastPos, matchStart).append(replacement);
+                        lastPos = matchEnd;
+                    }
+                }
+                builder.append(source, lastPos, source.length());
+                return builder.toString();
+            }
+
+            private String param(Object[] params, int index) {
+                if (index >= 0 && index < params.length) {
+                    return dynamic.coerceToString(params[index]);
+                }
+                return "";
+            }
+
+            private int placeholderIndex(String placeholder) {
+                return Integer.parseInt(placeholder.substring(1, placeholder.length() - 1));
+            }
+        };
+    }
+}

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/I18nFilter.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/I18nFilter.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/I18nFilter.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/I18nFilter.java Fri Nov 14 14:04:56 2014
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * 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.filter;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+
+import org.apache.sling.scripting.sightly.compiler.api.Filter;
+import org.apache.sling.scripting.sightly.compiler.api.expression.Expression;
+import org.apache.sling.scripting.sightly.compiler.api.expression.ExpressionNode;
+import org.apache.sling.scripting.sightly.compiler.api.expression.node.NullLiteral;
+import org.apache.sling.scripting.sightly.compiler.api.expression.node.RuntimeCall;
+import org.apache.sling.scripting.sightly.compiler.api.Filter;
+import org.apache.sling.scripting.sightly.compiler.api.expression.Expression;
+import org.apache.sling.scripting.sightly.compiler.api.expression.node.NullLiteral;
+import org.apache.sling.scripting.sightly.compiler.api.expression.node.RuntimeCall;
+
+/**
+ * Filter for i18n translation
+ */
+@Component
+@Service(Filter.class)
+@Property(name = FilterComponent.PRIORITY, intValue = 90)
+public class I18nFilter extends FilterComponent {
+
+    public static final String FUNCTION = "i18nTranslation";
+
+    public static final String I18N_OPTION = "i18n";
+    public static final String HINT_OPTION = "hint";
+    public static final String LOCALE_OPTION = "locale";
+
+    @Override
+    public Expression apply(Expression expression) {
+        if (!expression.containsOption(I18N_OPTION)) {
+            return expression;
+        }
+        ExpressionNode hint = option(expression, HINT_OPTION);
+        ExpressionNode locale = option(expression, LOCALE_OPTION);
+        ExpressionNode translation = new RuntimeCall(FUNCTION, expression.getRoot(), locale, hint);
+        return expression.withNode(translation).removeOptions(HINT_OPTION, LOCALE_OPTION);
+    }
+
+    private ExpressionNode option(Expression expression, String optionName) {
+        ExpressionNode node = expression.getOption(optionName);
+        if (node == null) {
+            return NullLiteral.INSTANCE;
+        }
+        return node;
+    }
+}

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/JoinFilter.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/JoinFilter.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/JoinFilter.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/JoinFilter.java Fri Nov 14 14:04:56 2014
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * 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.filter;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+
+import org.apache.sling.scripting.sightly.api.ExtensionInstance;
+import org.apache.sling.scripting.sightly.api.RenderContext;
+import org.apache.sling.scripting.sightly.api.RuntimeExtension;
+import org.apache.sling.scripting.sightly.api.RuntimeExtensionException;
+import org.apache.sling.scripting.sightly.compiler.api.Filter;
+import org.apache.sling.scripting.sightly.compiler.api.expression.Expression;
+import org.apache.sling.scripting.sightly.compiler.api.expression.ExpressionNode;
+import org.apache.sling.scripting.sightly.compiler.api.expression.node.RuntimeCall;
+import org.apache.sling.scripting.sightly.common.Dynamic;
+import org.apache.sling.scripting.sightly.api.ExtensionInstance;
+import org.apache.sling.scripting.sightly.api.RenderContext;
+import org.apache.sling.scripting.sightly.api.RuntimeExtension;
+import org.apache.sling.scripting.sightly.api.RuntimeExtensionException;
+import org.apache.sling.scripting.sightly.common.Dynamic;
+import org.apache.sling.scripting.sightly.compiler.api.Filter;
+import org.apache.sling.scripting.sightly.compiler.api.expression.Expression;
+import org.apache.sling.scripting.sightly.compiler.api.expression.ExpressionNode;
+import org.apache.sling.scripting.sightly.compiler.api.expression.node.RuntimeCall;
+
+/**
+ * Filter providing support for the {@code join} option applied to arrays.
+ */
+@Component
+@Service({Filter.class, RuntimeExtension.class})
+public class JoinFilter extends FilterComponent implements RuntimeExtension {
+
+    public static final String JOIN_OPTION = "join";
+    public static final String JOIN_FUNCTION = "join";
+
+    @Override
+    public Expression apply(Expression expression) {
+        if (!expression.containsOption(JOIN_OPTION)) {
+            return expression;
+        }
+        ExpressionNode argumentNode = expression.getOption(JOIN_OPTION);
+        ExpressionNode joinResult = new RuntimeCall(JOIN_FUNCTION, expression.getRoot(), argumentNode);
+        return expression.withNode(joinResult).removeOptions(JOIN_OPTION);
+    }
+
+    @Override
+    public String name() {
+        return JOIN_FUNCTION;
+    }
+
+    @Override
+    public ExtensionInstance provide(RenderContext renderContext) {
+        final Dynamic dynamic = new Dynamic(renderContext.getObjectModel());
+
+        return new ExtensionInstance() {
+            @Override
+            public Object call(Object... arguments) {
+                if (arguments.length != 2) {
+                    throw new RuntimeExtensionException("Join function must be called with two arguments.");
+                }
+                Collection<?> collection = dynamic.coerceToCollection(arguments[0]);
+                String joinString = dynamic.coerceToString(arguments[1]);
+                return join(collection, joinString);
+            }
+
+            private String join(Collection<?> collection, String joinString) {
+                StringBuilder sb = new StringBuilder();
+                Iterator<?> iterator = collection.iterator();
+                while (iterator.hasNext()) {
+                    String element = dynamic.coerceToString(iterator.next());
+                    sb.append(element);
+                    if (iterator.hasNext()) {
+                        sb.append(joinString);
+                    }
+                }
+                return sb.toString();
+            }
+        };
+
+    }
+}

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/XSSFilter.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/XSSFilter.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/XSSFilter.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/filter/XSSFilter.java Fri Nov 14 14:04:56 2014
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.filter;
+
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+
+import org.apache.sling.scripting.sightly.compiler.Syntax;
+import org.apache.sling.scripting.sightly.compiler.api.Filter;
+import org.apache.sling.scripting.sightly.compiler.api.expression.Expression;
+import org.apache.sling.scripting.sightly.compiler.api.expression.ExpressionNode;
+import org.apache.sling.scripting.sightly.compiler.api.expression.node.RuntimeCall;
+import org.apache.sling.scripting.sightly.compiler.Syntax;
+import org.apache.sling.scripting.sightly.compiler.api.Filter;
+import org.apache.sling.scripting.sightly.compiler.api.expression.Expression;
+
+/**
+ * XSS filter implementation
+ */
+@Component
+@Service(Filter.class)
+@Property(name = FilterComponent.PRIORITY, intValue = 110)
+public class XSSFilter extends FilterComponent {
+
+    public static final String FUNCTION_NAME = "xss";
+
+    @Override
+    public Expression apply(Expression expression) {
+        ExpressionNode node = expression.getRoot();
+        Map<String, ExpressionNode> options = expression.getOptions();
+        ExpressionNode context = options.get(Syntax.CONTEXT_OPTION);
+        if (context != null) {
+            return new Expression(new RuntimeCall(FUNCTION_NAME, node, context), options);
+        }
+        return expression;
+    }
+
+}

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/MarkupUtils.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/MarkupUtils.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/MarkupUtils.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/MarkupUtils.java Fri Nov 14 14:04:56 2014
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.html;
+
+import java.util.regex.Pattern;
+
+/**
+ * Utility methods related to markup
+ */
+public class MarkupUtils {
+
+    private static final Pattern ATTRIBUTE_BLACKLIST = Pattern.compile("^(style|(on.*))$", Pattern.CASE_INSENSITIVE);
+
+    /**
+     * Attributes which should not be generated by Sightly
+     * @param name the name of the attribute
+     * @return if the attribute is sensitive or not
+     */
+    public static boolean isSensitiveAttribute(String name) {
+        return ATTRIBUTE_BLACKLIST.matcher(name).matches();
+    }
+
+}

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/AttributeList.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/AttributeList.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/AttributeList.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/AttributeList.java Fri Nov 14 14:04:56 2014
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * 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.html.dom;
+
+import java.util.Iterator;
+
+/**
+ * Contains the list of attributes inside an HTML tag.
+ */
+public interface AttributeList {
+
+    /**
+     * Return the count of attributes
+     * @return count of attributes
+     */
+    int attributeCount();
+
+    /**
+     * Return the list of attribute names
+     * @return <code>Iterator</code> iterating over the attribute names
+     */
+    Iterator<String> attributeNames();
+
+    /**
+     * Return a flag indicating whether a specified attribute exists
+     * @return <code>true</code> if the specified attribute exists,
+     * <code>false</code> otherwise
+     */
+    boolean containsAttribute(String name);
+
+    /**
+     * Return an attribute's value, given its name or <code>null</code>
+     * if the attribute cannot be found.
+     * @param name   attribute name
+     * @return an attribute's value
+     */
+    String getValue(String name);
+
+    /**
+     * Return an attribute's quote character, given its name or <code>0</code>
+     * if the attribute cannot be found.
+     * @param name   attribute name
+     * @return an attribute's quote character
+     */
+    char getQuoteChar(String name);
+
+    /**
+     * Return an attribute's value, already surrounded with the quotes
+     * originally in place. Returns <code>null</code> if the attribute
+     * cannot be found
+     * @param name   attribute name
+     * @return an attribute's value
+     */
+    String getQuotedValue(String name);
+
+    /**
+     * Set an attribute's value. If the value is <code>null</code>, this
+     * is semantically different to a {@link #removeValue(String)}.
+     *
+     * @param name      attribute name
+     * @param value     attribute value
+     */
+    void setValue(String name, String value);
+
+    /**
+     * Remove an attribute's value.
+     * @param name      attribute name
+     */
+    void removeValue(String name);
+
+    /**
+     * Return a flag indicating whether this object was modified.
+     * @return <code>true</code> if the object was modified
+     *         <code>false</code> otherwise
+     */
+    boolean isModified();
+}
\ No newline at end of file

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/DocumentHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/DocumentHandler.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/DocumentHandler.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/DocumentHandler.java Fri Nov 14 14:04:56 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.html.dom;
+
+import java.io.IOException;
+
+
+/**
+ * Invoked by the <code>HTMLParser</code> when elements are scanned.
+ */
+public interface DocumentHandler {
+
+    /**
+     * Receive notification of unparsed character data.
+     */
+    void onCharacters(char[] ch, int off, int len) throws IOException;
+
+    void onComment(String characters) throws IOException;
+
+    /**
+     * Receive notification of the beginning of an element.
+     * @param name     tag name
+     * @param attList  attribute list
+     * @param endSlash flag indicating whether the element is closed with
+     *                 an ending slash (xhtml-compliant)
+     */
+    void onStartElement(String name, AttributeList attList, boolean endSlash)
+    throws IOException;
+
+    /**
+     * Receive notification of the end of an element.
+     * @param name tag name
+     */
+    void onEndElement(String name)
+    throws IOException;
+
+    /**
+     * Receive notification of parsing start.
+     */
+    void onStart() throws IOException;
+
+    /**
+     * Receive notification of parsing end.
+     */
+    void onEnd() throws IOException;
+}

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/HtmlParser.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/HtmlParser.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/HtmlParser.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/HtmlParser.java Fri Nov 14 14:04:56 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.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();
+    }
+}

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/HtmlParserService.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/HtmlParserService.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/HtmlParserService.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/HtmlParserService.java Fri Nov 14 14:04:56 2014
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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.html.dom;
+
+import org.apache.sling.scripting.sightly.html.dom.template.Template;
+import org.apache.sling.scripting.sightly.html.dom.template.TemplateParser;
+import org.apache.sling.scripting.sightly.compiler.api.MarkupHandler;
+import org.apache.sling.scripting.sightly.compiler.api.MarkupParser;
+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.compiler.api.MarkupHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+@Component
+@Properties({
+    @Property(name="service.description", value="Sightly Simple HTML parser"),
+    @Property(name="service.ranking", intValue=1000)
+})
+@Service(MarkupParser.class)
+public class HtmlParserService implements MarkupParser {
+
+    private static final Logger log = LoggerFactory.getLogger(HtmlParserService.class);
+
+    public void parse(String script, MarkupHandler handler) {
+        try {
+            final StringReader sr = new StringReader(script);
+            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);
+        }
+    }
+}

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/TagTokenizer.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/TagTokenizer.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/TagTokenizer.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/TagTokenizer.java Fri Nov 14 14:04:56 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.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

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/TreeTraverser.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/TreeTraverser.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/TreeTraverser.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/TreeTraverser.java Fri Nov 14 14:04:56 2014
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * 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.html.dom;
+
+import org.apache.sling.scripting.sightly.compiler.api.MarkupHandler;
+import org.apache.sling.scripting.sightly.html.dom.template.Template;
+import org.apache.sling.scripting.sightly.html.dom.template.TemplateElementNode;
+import org.apache.sling.scripting.sightly.html.dom.template.TemplateNode;
+import org.apache.sling.scripting.sightly.html.dom.template.TemplateTextNode;
+import org.apache.sling.scripting.sightly.html.dom.template.TemplateCommentNode;
+import org.apache.sling.scripting.sightly.html.dom.template.TemplateAttribute;
+import org.apache.sling.scripting.sightly.compiler.api.MarkupHandler;
+
+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);
+        }
+    }
+
+}

Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/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/html/dom/template/Template.java?rev=1639641&view=auto
==============================================================================
--- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/template/Template.java (added)
+++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/html/dom/template/Template.java Fri Nov 14 14:04:56 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.html.dom.template;
+
+
+public class Template extends TemplateElementNode {
+
+    public Template() {
+        super("ROOT", false, null);
+    }
+}