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}