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; 018 019import java.util.Deque; 020import java.util.LinkedList; 021 022import org.apache.wicket.MarkupContainer; 023import org.apache.wicket.util.lang.Args; 024import org.apache.wicket.util.value.IValueMap; 025import org.apache.wicket.util.value.ValueMap; 026 027 028/** 029 * Some utils to handle tags which otherwise would bloat the Tag API. 030 * 031 * @author Juergen Donnerstag 032 */ 033public class TagUtils 034{ 035 private static final String DEFAULT_ATTRIBUTE_SEPARATOR = "; "; 036 /** 037 * A map that keeps the separators which should be used for the different HTML 038 * element attributes. 039 */ 040 // 'public' so that user applications can add/modify the entries, if needed 041 public static final IValueMap ATTRIBUTES_SEPARATORS = new ValueMap(); 042 static { 043 ATTRIBUTES_SEPARATORS.put("class", " "); 044 ATTRIBUTES_SEPARATORS.put("style", DEFAULT_ATTRIBUTE_SEPARATOR); 045 ATTRIBUTES_SEPARATORS.put("onclick", DEFAULT_ATTRIBUTE_SEPARATOR); 046 } 047 048 /** 049 * Constructor 050 */ 051 public TagUtils() 052 { 053 } 054 055 /** 056 * @return True, if tag name equals '<body ...>' 057 * 058 * @param tag 059 */ 060 public static boolean isBodyTag(final ComponentTag tag) 061 { 062 return ("body".equalsIgnoreCase(tag.getName()) && (tag.getNamespace() == null)); 063 } 064 065 /** 066 * 067 * @param elem 068 * @return True, if tag name equals '<head ...>' 069 */ 070 public static boolean isHeadTag(final MarkupElement elem) 071 { 072 if (elem instanceof ComponentTag) 073 { 074 ComponentTag tag = (ComponentTag)elem; 075 if ("head".equalsIgnoreCase(tag.getName()) && (tag.getNamespace() == null)) 076 { 077 return true; 078 } 079 } 080 return false; 081 } 082 083 /** 084 * 085 * @param markup 086 * @param i 087 * @return True if the markup element at index 'i' is a WicketTag 088 */ 089 public static boolean isWicketTag(final IMarkupFragment markup, final int i) 090 { 091 MarkupElement elem = markup.get(i); 092 return elem instanceof WicketTag; 093 } 094 095 /** 096 * 097 * @param markup 098 * @param i 099 * @return True if the markup element at index 'i' is a <wicket:extend> tag 100 */ 101 public static boolean isExtendTag(final IMarkupFragment markup, final int i) 102 { 103 MarkupElement elem = markup.get(i); 104 if (elem instanceof WicketTag) 105 { 106 WicketTag wtag = (WicketTag)elem; 107 return wtag.isExtendTag(); 108 } 109 return false; 110 } 111 112 /** 113 * 114 * @param elem 115 * @return True if the current markup element is a <wicket:head> tag 116 */ 117 public static boolean isWicketHeadTag(final MarkupElement elem) 118 { 119 if (elem instanceof WicketTag) 120 { 121 WicketTag wtag = (WicketTag)elem; 122 if (wtag.isHeadTag()) 123 { 124 return true; 125 } 126 } 127 return false; 128 } 129 130 /** 131 * 132 * @param elem 133 * @return True if the current markup element is a <wicket:header-items> tag 134 */ 135 public static boolean isWicketHeaderItemsTag(final MarkupElement elem) 136 { 137 if (elem instanceof WicketTag) 138 { 139 WicketTag wtag = (WicketTag)elem; 140 if (wtag.isHeaderItemsTag()) 141 { 142 return true; 143 } 144 } 145 return false; 146 } 147 148 /** 149 * 150 * @param elem 151 * @return True if the current markup element is a <wicket:body> tag 152 */ 153 public static boolean isWicketBodyTag(final MarkupElement elem) 154 { 155 if (elem instanceof WicketTag) 156 { 157 WicketTag wtag = (WicketTag)elem; 158 if (wtag.isBodyTag()) 159 { 160 return true; 161 } 162 } 163 return false; 164 } 165 166 /** 167 * 168 * @param elem 169 * @return True if the current markup element is a <wicket:border> tag 170 */ 171 public static boolean isWicketBorderTag(final MarkupElement elem) 172 { 173 if (elem instanceof WicketTag) 174 { 175 WicketTag wtag = (WicketTag)elem; 176 if (wtag.isBorderTag()) 177 { 178 return true; 179 } 180 } 181 return false; 182 } 183 184 /** 185 * Copy attributes from e.g. <wicket:panel> (or border) to the "calling" tag. 186 * 187 * @see <a href="http://issues.apache.org/jira/browse/WICKET-2874">WICKET-2874</a> 188 * @see <a href="https://issues.apache.org/jira/browse/WICKET-3812">WICKET-3812</a> 189 * 190 * @param component 191 * the markup container which attributes will be copied 192 * @param tag 193 * the component tag where the attributes will be applied 194 */ 195 public static void copyAttributes(final MarkupContainer component, final ComponentTag tag) 196 { 197 IMarkupFragment markup = component.getMarkup(null); 198 String namespace = markup.getMarkupResourceStream().getWicketNamespace() + ":"; 199 200 MarkupElement elem = markup.get(0); 201 if (elem instanceof ComponentTag) 202 { 203 ComponentTag panelTag = (ComponentTag)elem; 204 for (String key : panelTag.getAttributes().keySet()) 205 { 206 // exclude "wicket:XX" attributes 207 if (key.startsWith(namespace) == false) 208 { 209 String separator = ATTRIBUTES_SEPARATORS.getString(key, DEFAULT_ATTRIBUTE_SEPARATOR); 210 tag.append(key, panelTag.getAttribute(key), separator); 211 } 212 } 213 } 214 else 215 { 216 throw new MarkupException(markup.getMarkupResourceStream(), 217 "Expected a Tag but found raw markup: " + elem.toString()); 218 } 219 } 220 221 /** 222 * Find the markup fragment of a tag with wicket:id equal to {@code id} starting at offset {@code streamOffset}. 223 * 224 * @param id 225 * The wicket:id of the tag being searched for. 226 * @param tagName 227 * The tag name of the tag being searched for. 228 * @param streamOffset 229 * The offset in the markup stream from which to start searching. 230 * @return the {@link IMarkupFragment} of the component tag if found, {@code null} is not found. 231 */ 232 public static final IMarkupFragment findTagMarkup(IMarkupFragment fragment, String id, String tagName, int streamOffset) 233 { 234 /* 235 * We need streamOffset because MarkupFragment starts searching from offset 1. 236 */ 237 Args.notEmpty(id, "id"); 238 Args.withinRange(0, fragment.size() - 1, streamOffset, "streamOffset"); 239 240 Deque<Boolean> openTagUsability = new LinkedList<>(); 241 boolean canFind = true; 242 243 MarkupStream stream = new MarkupStream(fragment); 244 stream.setCurrentIndex(streamOffset); 245 while (stream.isCurrentIndexInsideTheStream()) 246 { 247 MarkupElement elem = stream.get(); 248 249 if (elem instanceof ComponentTag) 250 { 251 ComponentTag tag = stream.getTag(); 252 253 if (tag.isOpen() || tag.isOpenClose()) 254 { 255 if (canFind && tag.getId().equals(id) && (tagName==null || tag.getName().equals(tagName))) 256 { 257 return stream.getMarkupFragment(); 258 } 259 else if (tag.isOpen() && !tag.hasNoCloseTag()) 260 { 261 openTagUsability.push(canFind); 262 263 if (tag instanceof WicketTag) 264 { 265 WicketTag wtag = (WicketTag)tag; 266 267 if (wtag.isExtendTag()) 268 { 269 canFind = true; 270 } 271 else if (wtag.isFragmentTag() || wtag.isContainerTag()) 272 { 273 canFind = false; 274 } 275 /* 276 * We should potentially also not try find child markup inside some other 277 * Wicket tags. Other tags that we should think about refusing to look for 278 * child markup inside include: container, body, border, child (we already 279 * have special extend handling). 280 */ 281 } 282 else if (!"head".equals(tag.getName()) && !tag.isAutoComponentTag()) 283 { 284 canFind = false; 285 } 286 } 287 } 288 else if (tag.isClose()) 289 { 290 if (openTagUsability.isEmpty()) 291 { 292 canFind = false; 293 } 294 else 295 { 296 canFind = openTagUsability.pop(); 297 } 298 } 299 } 300 stream.next(); 301 } 302 return null; 303 } 304}