001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.markup.parser.filter; 018 019import java.text.ParseException; 020import java.util.ArrayDeque; 021import java.util.Deque; 022import java.util.Iterator; 023 024import org.apache.wicket.Component; 025import org.apache.wicket.MarkupContainer; 026import org.apache.wicket.markup.ComponentTag; 027import org.apache.wicket.markup.MarkupElement; 028import org.apache.wicket.markup.MarkupResourceStream; 029import org.apache.wicket.markup.MarkupStream; 030import org.apache.wicket.markup.WicketTag; 031import org.apache.wicket.markup.html.internal.InlineEnclosure; 032import org.apache.wicket.markup.parser.AbstractMarkupFilter; 033import org.apache.wicket.markup.resolver.IComponentResolver; 034import org.apache.wicket.util.string.Strings; 035 036 037/** 038 * This is a markup inline filter. It identifies enclosures as attribute, for example: <tr 039 * wicket:enclosure="">. The <tr> tag used in the example can be replaced with any html tag 040 * that can contain child elements. If the 'child' attribute is empty it determines the wicket:id of 041 * the child component automatically by analyzing the wicket component (in this case one wicket 042 * component is allowed) in between the open and close tags. If the enclosure tag has a 'child' 043 * attribute like <code><tr 044 * wicket:enclosure="controllingChildId"></code> than more than just one wicket component inside 045 * the enclosure tags are allowed and the child component which determines the visibility of the 046 * enclosure is identified by the 'child' attribute value which must be equal to the relative child 047 * id path. 048 * 049 * @see InlineEnclosure 050 * 051 * @author Joonas Hamalainen 052 * @author Juergen Donnerstag 053 */ 054public final class InlineEnclosureHandler extends AbstractMarkupFilter 055 implements 056 IComponentResolver 057{ 058 private static final long serialVersionUID = 1L; 059 060 /** The Component id prefix. */ 061 public final static String INLINE_ENCLOSURE_ID_PREFIX = "InlineEnclosure-"; 062 063 /** Attribute to identify inline enclosures */ 064 public final static String INLINE_ENCLOSURE_ATTRIBUTE_NAME = "enclosure"; 065 066 /** enclosures inside enclosures */ 067 private Deque<ComponentTag> enclosures; 068 069 /** 070 * Construct. 071 */ 072 public InlineEnclosureHandler() 073 { 074 this(null); 075 } 076 077 public InlineEnclosureHandler(MarkupResourceStream resourceStream) 078 { 079 super(resourceStream); 080 } 081 082 @Override 083 protected MarkupElement onComponentTag(final ComponentTag tag) throws ParseException 084 { 085 // We only need ComponentTags 086 if (tag instanceof WicketTag) 087 { 088 return tag; 089 } 090 091 // Has wicket:enclosure attribute? 092 String enclosureAttr = getAttribute(tag, null); 093 if (enclosureAttr != null) 094 { 095 if (tag.isOpen()) 096 { 097 // Make sure 'wicket:id' and 'id' are consistent 098 String htmlId = tag.getAttribute("id"); 099 if ((tag.getId() != null) && !Strings.isEmpty(htmlId) && 100 !htmlId.equals(tag.getId())) 101 { 102 throw new ParseException( 103 "Make sure that 'id' and 'wicket:id' are the same if both are provided. Tag:" + 104 tag.toString(), tag.getPos()); 105 } 106 107 // if it doesn't have a wicket-id already, then assign one now. 108 if (Strings.isEmpty(tag.getId())) 109 { 110 if (Strings.isEmpty(htmlId)) 111 { 112 String id = getWicketNamespace() + "_" + INLINE_ENCLOSURE_ID_PREFIX + 113 getRequestUniqueId(); 114 tag.setId(id); 115 } 116 else 117 { 118 tag.setId(htmlId); 119 } 120 121 tag.setAutoComponentTag(true); 122 tag.setAutoComponentFactory(new ComponentTag.IAutoComponentFactory() 123 { 124 @Override 125 public Component newComponent(MarkupContainer container, ComponentTag tag) 126 { 127 String attributeName = getInlineEnclosureAttributeName(null); 128 String childId = tag.getAttribute(attributeName); 129 return new InlineEnclosure(tag.getId(), childId); 130 } 131 }); 132 tag.setModified(true); 133 } 134 135 // Put the enclosure on the stack. The most current one will be on top 136 if (enclosures == null) 137 { 138 enclosures = new ArrayDeque<>(); 139 } 140 enclosures.push(tag); 141 } 142 else 143 { 144 throw new ParseException( 145 "Open-close tags don't make sense for InlineEnclosure. Tag:" + tag.toString(), 146 tag.getPos()); 147 } 148 } 149 // Are we within an enclosure? 150 else if ((enclosures != null) && (enclosures.size() > 0)) 151 { 152 // In case the enclosure tag did not provide a child component id, then assign the 153 // first ComponentTag's id found as the controlling child to the enclosure. 154 if (tag.isOpen() && (tag.getId() != null) && !(tag instanceof WicketTag) && 155 !tag.isAutoComponentTag()) 156 { 157 Iterator<ComponentTag> componentTagIterator = enclosures.descendingIterator(); 158 while (componentTagIterator.hasNext()) 159 { 160 ComponentTag lastEnclosure = componentTagIterator.next(); 161 String attr = getAttribute(lastEnclosure, null); 162 if (Strings.isEmpty(attr) == true) 163 { 164 lastEnclosure.getAttributes().put(getInlineEnclosureAttributeName(null), 165 tag.getId()); 166 lastEnclosure.setModified(true); 167 } 168 } 169 } 170 else if (tag.isClose() && tag.closes(enclosures.peek())) 171 { 172 ComponentTag lastEnclosure = enclosures.pop(); 173 String attr = getAttribute(lastEnclosure, null); 174 if (Strings.isEmpty(attr) == true) 175 { 176 throw new ParseException("Did not find any child for InlineEnclosure. Tag:" + 177 lastEnclosure.toString(), tag.getPos()); 178 } 179 } 180 } 181 182 return tag; 183 } 184 185 /** 186 * @param tag 187 * The ComponentTag of the markup element with wicket:enclosure attribute 188 * @return The value of wicket:enclosure attribute or null if not found 189 */ 190 private String getAttribute(final ComponentTag tag, MarkupStream markupStream) 191 { 192 return tag.getAttributes().getString(getInlineEnclosureAttributeName(markupStream)); 193 } 194 195 @Override 196 public Component resolve(final MarkupContainer container, final MarkupStream markupStream, 197 final ComponentTag tag) 198 { 199 String inlineEnclosureChildId = getAttribute(tag, markupStream); 200 if (Strings.isEmpty(inlineEnclosureChildId) == false) 201 { 202 String id = tag.getId(); 203 204 // Yes, we handled the tag 205 return new InlineEnclosure(id, inlineEnclosureChildId); 206 } 207 208 // We were not able to handle the tag 209 return null; 210 } 211 212 private String getInlineEnclosureAttributeName(MarkupStream markupStream) { 213 return getWicketNamespace(markupStream) + ':' + INLINE_ENCLOSURE_ATTRIBUTE_NAME; 214 } 215 216}