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.ArrayList;
020
021import org.apache.wicket.Application;
022import org.apache.wicket.Page;
023import org.apache.wicket.markup.html.form.AutoLabelTagHandler;
024import org.apache.wicket.markup.parser.IMarkupFilter;
025import org.apache.wicket.markup.parser.IXmlPullParser;
026import org.apache.wicket.markup.parser.filter.EnclosureHandler;
027import org.apache.wicket.markup.parser.filter.HeadForceTagIdHandler;
028import org.apache.wicket.markup.parser.filter.HtmlHandler;
029import org.apache.wicket.markup.parser.filter.HtmlHeaderSectionHandler;
030import org.apache.wicket.markup.parser.filter.InlineEnclosureHandler;
031import org.apache.wicket.markup.parser.filter.OpenCloseTagExpander;
032import org.apache.wicket.markup.parser.filter.RelativePathPrefixHandler;
033import org.apache.wicket.markup.parser.filter.StyleAndScriptIdentifier;
034import org.apache.wicket.markup.parser.filter.WicketContainerTagHandler;
035import org.apache.wicket.markup.parser.filter.WicketLinkTagHandler;
036import org.apache.wicket.markup.parser.filter.WicketMessageTagHandler;
037import org.apache.wicket.markup.parser.filter.WicketNamespaceHandler;
038import org.apache.wicket.markup.parser.filter.WicketRemoveTagHandler;
039import org.apache.wicket.markup.parser.filter.WicketTagIdentifier;
040import org.apache.wicket.util.lang.Objects;
041
042/**
043 * This is Wicket's default markup parser. It gets pre-configured with Wicket's default wicket
044 * filters.
045 * 
046 * @see MarkupFactory
047 * 
048 * @author Juergen Donnerstag
049 */
050public class MarkupParser extends AbstractMarkupParser
051{
052        /** "wicket" */
053        public final static String WICKET = "wicket";
054
055        /**
056         * Constructor.
057         * 
058         * @param resource
059         *            The markup resource (file)
060         */
061        public MarkupParser(final MarkupResourceStream resource)
062        {
063                super(resource);
064        }
065
066        /**
067         * Constructor. Usually for testing purposes only
068         * 
069         * @param markup
070         *            The markup resource.
071         */
072        public MarkupParser(final String markup)
073        {
074                super(markup);
075        }
076
077        /**
078         * Constructor.
079         * 
080         * @param xmlParser
081         *            The streaming xml parser to read and parse the markup
082         * @param resource
083         *            The markup resource (file)
084         */
085        public MarkupParser(final IXmlPullParser xmlParser, final MarkupResourceStream resource)
086        {
087                super(xmlParser, resource);
088        }
089
090        @Override
091        public MarkupFilterList getMarkupFilters()
092        {
093                return (MarkupFilterList)super.getMarkupFilters();
094        }
095
096        /**
097         * Add a markup filter
098         * 
099         * @param filter
100         * @return true, if successful
101         */
102        public final boolean add(final IMarkupFilter filter)
103        {
104                return getMarkupFilters().add(filter);
105        }
106
107        /**
108         * Add a markup filter before the 'beforeFilter'
109         * 
110         * @param filter
111         * @param beforeFilter
112         * @return true, if successful
113         */
114        public final boolean add(final IMarkupFilter filter,
115                final Class<? extends IMarkupFilter> beforeFilter)
116        {
117                return getMarkupFilters().add(filter, beforeFilter);
118        }
119
120        /**
121         * a) Allow subclasses to configure individual Wicket filters
122         * <p>
123         * b) Allows to disable Wicket filters via returning false
124         * 
125         * @param filter
126         * @return The filter to be added. Null to ignore.
127         */
128        protected IMarkupFilter onAppendMarkupFilter(final IMarkupFilter filter)
129        {
130                return filter;
131        }
132
133        /**
134         * Initialize Wicket's MarkupParser with all necessary markup filters. You may subclass this
135         * method, to add your own filters to the list.
136         * 
137         * @param markup
138         * @return The list of markup filter
139         */
140        @Override
141        protected MarkupFilterList initializeMarkupFilters(final Markup markup)
142        {
143                // MarkupFilterList is a simple extension of ArrayList providing few additional helpers
144                final MarkupFilterList filters = new MarkupFilterList();
145
146                MarkupResourceStream markupResourceStream = markup.getMarkupResourceStream();
147
148                filters.add(new WicketTagIdentifier(markupResourceStream));
149                filters.add(new HtmlHandler());
150                filters.add(new WicketRemoveTagHandler(markupResourceStream));
151                filters.add(new WicketLinkTagHandler(markupResourceStream));
152                filters.add(new AutoLabelTagHandler(markupResourceStream));
153                filters.add(new WicketNamespaceHandler(markupResourceStream));
154                filters.add(new WicketMessageTagHandler(markupResourceStream));
155
156                // Provided the wicket component requesting the markup is known ...
157                if ((markupResourceStream != null) && (markupResourceStream.getResource() != null))
158                {
159                        final ContainerInfo containerInfo = markupResourceStream.getContainerInfo();
160                        if (containerInfo != null)
161                        {
162                                // Pages require additional handlers
163                                if (Page.class.isAssignableFrom(containerInfo.getContainerClass()))
164                                {
165                                        filters.add(new HtmlHeaderSectionHandler(markup));
166                                }
167
168                                filters.add(new HeadForceTagIdHandler(containerInfo.getContainerClass()));
169                        }
170                }
171
172                filters.add(new OpenCloseTagExpander());
173                filters.add(new RelativePathPrefixHandler(markupResourceStream));
174                filters.add(new EnclosureHandler(markupResourceStream));
175                filters.add(new InlineEnclosureHandler(markupResourceStream));
176
177                // Append it. See WICKET-4390
178                filters.add(new StyleAndScriptIdentifier(), StyleAndScriptIdentifier.class);
179                filters.add(new WicketContainerTagHandler(markupResourceStream, Application.get().usesDevelopmentConfig()));
180
181                return filters;
182        }
183
184        /**
185         * A simple extension to ArrayList to manage Wicket MarkupFilter's more easily
186         */
187        public class MarkupFilterList extends ArrayList<IMarkupFilter>
188        {
189                private static final long serialVersionUID = 1L;
190
191                @Override
192                public boolean add(final IMarkupFilter filter)
193                {
194                        return add(filter, RelativePathPrefixHandler.class);
195                }
196
197                /**
198                 * Insert a markup filter before a another one.
199                 * 
200                 * @param filter
201                 * @param beforeFilter
202                 * @return true, if successful
203                 */
204                public boolean add(IMarkupFilter filter, final Class<? extends IMarkupFilter> beforeFilter)
205                {
206                        filter = onAdd(filter);
207                        if (filter == null)
208                        {
209                                return false;
210                        }
211
212                        int index = firstIndexOfClass(beforeFilter);
213                        if (index < 0)
214                        {
215                                return super.add(filter);
216                        }
217
218                        super.add(index, filter);
219                        return true;
220                }
221
222                /**
223                 * Finds the index of the first entry which is from the same type as the passed
224                 * {@literal filterClass} argument.
225                 *
226                 * @param filterClass
227                 *      the class to search for
228                 * @return the index of the first match or -1 otherwise
229                 */
230                private int firstIndexOfClass(final Class<? extends IMarkupFilter> filterClass)
231                {
232                        int result = -1;
233                        if (filterClass != null)
234                        {
235                                final int size = size();
236                                for (int index = 0; index < size; index++) {
237                                        Class<? extends IMarkupFilter> currentFilterClass = get(index).getClass();
238                                        if (Objects.equal(filterClass, currentFilterClass))
239                                        {
240                                                result = index;
241                                                break;
242                                        }
243                                }
244                        }
245                        return result;
246                }
247
248                /**
249                 * a) Allow subclasses to configure individual Wicket filters which otherwise can not be
250                 * accessed.
251                 * <p>
252                 * b) Allows to disable Wicket filters via returning false
253                 * 
254                 * @param filter
255                 * @return The filter to be added. Null to ignore
256                 */
257                protected IMarkupFilter onAdd(final IMarkupFilter filter)
258                {
259                        return onAppendMarkupFilter(filter);
260                }
261        }
262}