/* * 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.wicket.markup.parser.filter; import java.text.ParseException; import java.util.ArrayDeque; import java.util.Deque; import java.util.Iterator; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.MarkupElement; import org.apache.wicket.markup.MarkupResourceStream; import org.apache.wicket.markup.MarkupStream; import org.apache.wicket.markup.WicketTag; import org.apache.wicket.markup.html.internal.InlineEnclosure; import org.apache.wicket.markup.parser.AbstractMarkupFilter; import org.apache.wicket.markup.resolver.IComponentResolver; import org.apache.wicket.util.string.Strings; /** * This is a markup inline filter. It identifies enclosures as attribute, for example: <tr * wicket:enclosure="">. The <tr> tag used in the example can be replaced with any html tag * that can contain child elements. If the 'child' attribute is empty it determines the wicket:id of * the child component automatically by analyzing the wicket component (in this case one wicket * component is allowed) in between the open and close tags. If the enclosure tag has a 'child' * attribute like <code><tr * wicket:enclosure="controllingChildId"></code> than more than just one wicket component inside * the enclosure tags are allowed and the child component which determines the visibility of the * enclosure is identified by the 'child' attribute value which must be equal to the relative child * id path. * * @see InlineEnclosure * * @author Joonas Hamalainen * @author Juergen Donnerstag */ public final class InlineEnclosureHandler extends AbstractMarkupFilter implements IComponentResolver { private static final long serialVersionUID = 1L; /** The Component id prefix. */ public final static String INLINE_ENCLOSURE_ID_PREFIX = "InlineEnclosure-"; /** Attribute to identify inline enclosures */ public final static String INLINE_ENCLOSURE_ATTRIBUTE_NAME = "enclosure"; /** enclosures inside enclosures */ private Deque<ComponentTag> enclosures; /** * Construct. */ public InlineEnclosureHandler() { this(null); } public InlineEnclosureHandler(MarkupResourceStream resourceStream) { super(resourceStream); } @Override protected MarkupElement onComponentTag(final ComponentTag tag) throws ParseException { // We only need ComponentTags if (tag instanceof WicketTag) { return tag; } // Has wicket:enclosure attribute? String enclosureAttr = getAttribute(tag, null); if (enclosureAttr != null) { if (tag.isOpen()) { // Make sure 'wicket:id' and 'id' are consistent String htmlId = tag.getAttribute("id"); if ((tag.getId() != null) && !Strings.isEmpty(htmlId) && !htmlId.equals(tag.getId())) { throw new ParseException( "Make sure that 'id' and 'wicket:id' are the same if both are provided. Tag:" + tag.toString(), tag.getPos()); } // if it doesn't have a wicket-id already, then assign one now. if (Strings.isEmpty(tag.getId())) { if (Strings.isEmpty(htmlId)) { String id = getWicketNamespace() + "_" + INLINE_ENCLOSURE_ID_PREFIX + getRequestUniqueId(); tag.setId(id); } else { tag.setId(htmlId); } tag.setAutoComponentTag(true); tag.setAutoComponentFactory(new ComponentTag.IAutoComponentFactory() { @Override public Component newComponent(MarkupContainer container, ComponentTag tag) { String attributeName = getInlineEnclosureAttributeName(null); String childId = tag.getAttribute(attributeName); return new InlineEnclosure(tag.getId(), childId); } }); tag.setModified(true); } // Put the enclosure on the stack. The most current one will be on top if (enclosures == null) { enclosures = new ArrayDeque<>(); } enclosures.push(tag); } else { throw new ParseException( "Open-close tags don't make sense for InlineEnclosure. Tag:" + tag.toString(), tag.getPos()); } } // Are we within an enclosure? else if ((enclosures != null) && (enclosures.size() > 0)) { // In case the enclosure tag did not provide a child component id, then assign the // first ComponentTag's id found as the controlling child to the enclosure. if (tag.isOpen() && (tag.getId() != null) && !(tag instanceof WicketTag) && !tag.isAutoComponentTag()) { Iterator<ComponentTag> componentTagIterator = enclosures.descendingIterator(); while (componentTagIterator.hasNext()) { ComponentTag lastEnclosure = componentTagIterator.next(); String attr = getAttribute(lastEnclosure, null); if (Strings.isEmpty(attr) == true) { lastEnclosure.getAttributes().put(getInlineEnclosureAttributeName(null), tag.getId()); lastEnclosure.setModified(true); } } } else if (tag.isClose() && tag.closes(enclosures.peek())) { ComponentTag lastEnclosure = enclosures.pop(); String attr = getAttribute(lastEnclosure, null); if (Strings.isEmpty(attr) == true) { throw new ParseException("Did not find any child for InlineEnclosure. Tag:" + lastEnclosure.toString(), tag.getPos()); } } } return tag; } /** * @param tag * The ComponentTag of the markup element with wicket:enclosure attribute * @return The value of wicket:enclosure attribute or null if not found */ private String getAttribute(final ComponentTag tag, MarkupStream markupStream) { return tag.getAttributes().getString(getInlineEnclosureAttributeName(markupStream)); } @Override public Component resolve(final MarkupContainer container, final MarkupStream markupStream, final ComponentTag tag) { String inlineEnclosureChildId = getAttribute(tag, markupStream); if (Strings.isEmpty(inlineEnclosureChildId) == false) { String id = tag.getId(); // Yes, we handled the tag return new InlineEnclosure(id, inlineEnclosureChildId); } // We were not able to handle the tag return null; } private String getInlineEnclosureAttributeName(MarkupStream markupStream) { return getWicketNamespace(markupStream) + ':' + INLINE_ENCLOSURE_ATTRIBUTE_NAME; } }