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;
020
021import org.apache.wicket.Component;
022import org.apache.wicket.MarkupContainer;
023import org.apache.wicket.markup.ComponentTag;
024import org.apache.wicket.markup.ComponentTag.IAutoComponentFactory;
025import org.apache.wicket.markup.Markup;
026import org.apache.wicket.markup.MarkupElement;
027import org.apache.wicket.markup.MarkupException;
028import org.apache.wicket.markup.MarkupStream;
029import org.apache.wicket.markup.html.internal.HtmlHeaderContainer;
030import org.apache.wicket.markup.html.internal.HtmlHeaderItemsContainer;
031import org.apache.wicket.markup.parser.AbstractMarkupFilter;
032import org.apache.wicket.markup.parser.XmlTag.TagType;
033import org.apache.wicket.markup.resolver.HtmlHeaderResolver;
034import org.apache.wicket.util.tester.BaseWicketTester;
035
036
037/**
038 * This is a markup inline filter.
039 * <p>
040 * It assumes that {@link org.apache.wicket.markup.parser.filter.WicketTagIdentifier}
041 * has been called first and search for a &lt;head&gt; tag (note: not wicket:head). Provided the markup contains a
042 * &lt;body&gt; tag it will automatically prepend a &lt;head&gt; tag if missing.
043 * </p>
044 * <p>
045 * Additionally this filter handles &lt;wicket:header-items/&gt;. If there is such tag then it is marked
046 * as the one that should be used as {@link org.apache.wicket.markup.html.internal.HtmlHeaderContainer}, by
047 * setting its id to {@value #HEADER_ID}.
048 * </p>
049 * <p>
050 * Note: This handler is only relevant for Pages (see MarkupParser.newFilterChain())
051 * 
052 * @see org.apache.wicket.markup.MarkupParser
053 * @see org.apache.wicket.markup.resolver.HtmlHeaderResolver
054 * @author Juergen Donnerstag
055 */
056public final class HtmlHeaderSectionHandler extends AbstractMarkupFilter
057{
058        public static final String BODY = "body";
059        public static final String HEAD = "head";
060
061        /** The automatically assigned wicket:id to &gt;head&lt; tag */
062        public static final String HEADER_ID = "_header_";
063
064        public static final String HEADER_ID_ITEM = "_header_item_";
065
066        /** True if &lt;head&gt; has been found already */
067        private boolean foundHead = false;
068
069        /** True if &lt;/head&gt; has been found already */
070        private boolean foundClosingHead = false;
071
072        /** True if &lt;/wicket:header-items&gt; has been found already */
073        private boolean foundHeaderItemsTag = false;
074
075        /** True if all the rest of the markup file can be ignored */
076        private boolean ignoreTheRest = false;
077
078        /** The Markup available so far for the resource */
079        private final Markup markup;
080
081        private static final IAutoComponentFactory HTML_HEADER_FACTORY = new IAutoComponentFactory()
082        {
083                @Override
084                public Component newComponent(MarkupContainer container, ComponentTag tag)
085                {
086                        return new HtmlHeaderContainer(tag.getId());
087                }
088        };
089        
090        private static final IAutoComponentFactory HTML_HEADER_ITEMS_FACTORY = new IAutoComponentFactory()
091        {
092                @Override
093                public Component newComponent(MarkupContainer container, ComponentTag tag)
094                {
095                        return new HtmlHeaderItemsContainer(tag.getId());
096                }
097        };
098        
099        /**
100         * Construct.
101         * 
102         * @param markup
103         *            The Markup object being filled while reading the markup resource
104         */
105        public HtmlHeaderSectionHandler(final Markup markup)
106        {
107                super(markup.getMarkupResourceStream());
108                this.markup = markup;
109        }
110
111        @Override
112        protected final MarkupElement onComponentTag(ComponentTag tag) throws ParseException
113        {
114                // Whatever there is left in the markup, ignore it
115                if (ignoreTheRest == true)
116                {
117                        return tag;
118                }
119
120                // if it is <head> or </head>
121                if (HEAD.equalsIgnoreCase(tag.getName()))
122                {
123                        if (tag.getNamespace() == null)
124                        {
125                                handleHeadTag(tag);
126                        }
127                        else
128                        {
129                                // we found <wicket:head>
130                                foundHead = true;
131                                foundClosingHead = true;
132                        }
133                }
134                else if (HtmlHeaderResolver.HEADER_ITEMS.equalsIgnoreCase(tag.getName()) &&
135                                tag.getNamespace().equalsIgnoreCase(getWicketNamespace()))
136                {
137                        handleHeaderItemsTag(tag);
138                }
139                else if (BODY.equalsIgnoreCase(tag.getName()) && (tag.getNamespace() == null))
140                {
141                        handleBodyTag();
142                }
143
144                return tag;
145        }
146
147        /**
148         * Handle tag &lt;body&gt;
149         */
150        private void handleBodyTag()
151        {
152                // WICKET-4511: We found <body> inside <head> tag. Markup is not valid!
153                if (foundHead && !foundClosingHead)
154                {
155                        throw new MarkupException(new MarkupStream(markup),
156                                "Invalid page markup. Tag <BODY> found inside <HEAD>");
157                }
158
159                // We found <body>
160                if (foundHead == false)
161                {
162                        insertHeadTag();
163                }
164
165                // <head> must always be before <body>
166                ignoreTheRest = true;
167        }
168
169        /**
170         * Handle tag &lt;wicket:header-items&gt;
171         * 
172         * @param tag
173         */
174        private void handleHeaderItemsTag(ComponentTag tag)
175        {
176                if ((tag.isOpen() || tag.isOpenClose()) && foundHeaderItemsTag)
177                {
178                        throw new MarkupException(new MarkupStream(markup),
179                                        "More than one <wicket:header-items/> detected in the <head> element. Only one is allowed.");
180                }
181                else if (foundClosingHead)
182                {
183                        throw new MarkupException(new MarkupStream(markup),
184                                        "Detected <wicket:header-items/> after the closing </head> element.");
185                }
186
187                foundHeaderItemsTag = true;
188                tag.setId(HEADER_ID);
189                tag.setAutoComponentTag(true);
190                tag.setModified(true);
191                tag.setAutoComponentFactory(HTML_HEADER_ITEMS_FACTORY);
192        }
193
194        /**
195         * Handle tag &lt;head&gt;
196         * @param tag
197         */
198        private void handleHeadTag(ComponentTag tag)
199        {
200                // we found <head>
201                if (tag.isOpen())
202                {
203                        if(foundHead)
204                        {
205                                throw new MarkupException(new MarkupStream(markup),
206                                        "Tag <head> is not allowed at this position (do you have multiple <head> tags in your markup?).");
207                        }
208                        
209                        foundHead = true;
210
211                        if (tag.getId() == null)
212                        {
213                                tag.setId(HEADER_ID);
214                                tag.setAutoComponentTag(true);
215                                tag.setModified(true);
216                                tag.setAutoComponentFactory(HTML_HEADER_FACTORY);
217                        }
218                }
219                else if (tag.isClose())
220                {
221                        if (foundHeaderItemsTag)
222                        {
223                                // revert the settings from above
224                                ComponentTag headOpenTag = tag.getOpenTag();
225                                // change the id because it is special. See HtmlHeaderResolver
226                                headOpenTag.setId(HEADER_ID + "-Ignored");
227                                headOpenTag.setAutoComponentTag(false);
228                                headOpenTag.setModified(false);
229                                headOpenTag.setFlag(ComponentTag.RENDER_RAW, true);
230                                headOpenTag.setAutoComponentFactory(null);
231                        }
232
233                        foundClosingHead = true;
234                }
235        }
236
237        /**
238         * Insert &lt;head&gt; open and close tag (with empty body) to the current position.
239         */
240        private void insertHeadTag()
241        {
242                // Note: only the open-tag must be a AutoComponentTag
243                final ComponentTag openTag = new ComponentTag(HEAD, TagType.OPEN);
244                openTag.setId(HEADER_ID);
245                openTag.setAutoComponentTag(true);
246                openTag.setModified(true);
247                openTag.setAutoComponentFactory(HTML_HEADER_FACTORY);
248
249                final ComponentTag closeTag = new ComponentTag(HEAD, TagType.CLOSE);
250                closeTag.setOpenTag(openTag);
251                closeTag.setModified(true);
252
253                // insert the tags into the markup stream
254                markup.addMarkupElement(openTag);
255                markup.addMarkupElement(closeTag);
256        }
257}