/******************************************************************************* * * Copyright (c) 2004-2010 Oracle Corporation. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * *******************************************************************************/ package hudson.util.jelly; import org.apache.commons.jelly.JellyContext; import org.apache.commons.jelly.JellyException; import org.apache.commons.jelly.JellyTagException; import org.apache.commons.jelly.Tag; import org.apache.commons.jelly.TagLibrary; import org.apache.commons.jelly.XMLOutput; import org.apache.commons.jelly.expression.Expression; import org.apache.commons.jelly.impl.ExpressionAttribute; import org.apache.commons.jelly.impl.TagScript; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Map; /** * Jelly tag library for literal-like tags, with an ability to add arbitrary * attributes taken from a map. * * <p> Tags from this namespace ("jelly:hudson.util.jelly.MorphTagLibrary") * behaves mostly like literal static tags, except it interprets two attributes * "ATTRIBUTES" and "EXCEPT" in a special way. * * The "ATTRIBUTES" attribute should have a Jelly expression that points to a * {@link Map} object, and the contents of the map are added as attributes of * this tag, with the exceptions of entries whose key values are listed in the * "EXCEPT" attribute. * * The "EXCEPT" attribute takes a white-space separated list of attribute names * that should be ignored even if it's in the map. * * <p> The explicit literal attributes, if specified, always take precedence * over the dynamic attributes added by the map. * * <p> See textbox.jelly as an example of using this tag library. * * @author Kohsuke Kawaguchi * @since 1.342 */ public class MorphTagLibrary extends TagLibrary { /** * This code is really only used for dealing with dynamic tag libraries, so * no point in implementing this for statically used tag libraries. */ @Override public Tag createTag(final String name, Attributes attributes) throws JellyException { return null; } @Override public TagScript createTagScript(final String tagName, Attributes attributes) throws JellyException { return new TagScript() { private Object evalAttribute(String name, JellyContext context) { ExpressionAttribute e = attributes.get(name); if (e == null) { return null; } return e.exp.evaluate(context); } private Collection<?> getExclusions(JellyContext context) { Object exclusion = evalAttribute(EXCEPT_ATTRIBUTES, context); if (exclusion == null) { return Collections.emptySet(); } if (exclusion instanceof String) { return Arrays.asList(exclusion.toString().split("\\s+")); // split by whitespace } if (exclusion instanceof Collection) { return (Collection) exclusion; } throw new IllegalArgumentException("Expected collection for exclusion but found :" + exclusion); } @Override public void run(JellyContext context, XMLOutput output) throws JellyTagException { AttributesImpl actual = new AttributesImpl(); Collection<?> exclusions = getExclusions(context); Map<String, ?> meta = (Map) evalAttribute(META_ATTRIBUTES, context); if (meta != null) { for (Map.Entry<String, ?> e : meta.entrySet()) { String key = e.getKey(); // @see jelly.impl.DynamicTag.setAttribute() -- ${attrs} has duplicates with "Attr" suffix if (key.endsWith("Attr") && meta.containsKey(key.substring(0, key.length() - 4))) { continue; } // @see http://github.com/hudson/jelly/commit/4ae67d15957b5b4d32751619997a3cb2a6ad56ed if (key.equals("ownerTag")) { continue; } if (!exclusions.contains(key)) { Object v = e.getValue(); if (v != null) { actual.addAttribute("", key, key, "CDATA", v.toString()); } } } } else { meta = Collections.emptyMap(); } for (Map.Entry<String, ExpressionAttribute> e : attributes.entrySet()) { String name = e.getKey(); if (name.equals(META_ATTRIBUTES) || name.equals(EXCEPT_ATTRIBUTES)) { continue; // already handled } if (meta.containsKey(name)) { // if the explicit value is also generated by a map, delete it first. // this is O(N) operation, but we don't expect there to be a lot of collisions. int idx = actual.getIndex(name); if (idx >= 0) { actual.removeAttribute(idx); } } Expression expression = e.getValue().exp; actual.addAttribute("", name, name, "CDATA", expression.evaluateAsString(context)); } try { output.startElement(tagName, actual); getTagBody().run(context, output); output.endElement(tagName); } catch (SAXException x) { throw new JellyTagException(x); } } }; } private static final String META_ATTRIBUTES = "ATTRIBUTES"; private static final String EXCEPT_ATTRIBUTES = "EXCEPT"; }