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.Locale;
021
022import org.apache.wicket.markup.ComponentTag;
023import org.apache.wicket.markup.MarkupElement;
024import org.apache.wicket.markup.WicketTag;
025import org.apache.wicket.markup.parser.AbstractMarkupFilter;
026import org.apache.wicket.util.string.AppendingStringBuffer;
027
028
029/**
030 * Handler that sets unique tag id for every inline script and style element in <wicket:head>,
031 * unless the element already has one. <br/>
032 * This is needed to be able to detect multiple ajax header contribution. Tags that are not inline
033 * (stript with src attribute set and link with href attribute set) do not require id, because the
034 * detection is done by comparing URLs.
035 * <p>
036 * Tags with wicket:id are <strong>not processed</strong>. To setOutputWicketId(true) on attached
037 * component is developer's responsibility. FIXME: Really? And if so, document properly
038 * 
039 * @author Matej Knopp
040 */
041public class HeadForceTagIdHandler extends AbstractMarkupFilter
042{
043        /** Common prefix for all id's generated by this filter */
044        private final String headElementIdPrefix;
045
046        /** Unique value per markup file */
047        private int counter = 0;
048
049        /** we are in wicket:head */
050        private boolean inHead = false;
051
052        /**
053         * Construct.
054         * 
055         * @param markupFileClass
056         *            Used to generated the a common prefix for the id
057         */
058        public HeadForceTagIdHandler(final Class<?> markupFileClass)
059        {
060                // generate the prefix from class name
061                final AppendingStringBuffer buffer = new AppendingStringBuffer(markupFileClass.getName());
062                for (int i = 0; i < buffer.getValue().length; ++i)
063                {
064                        if (Character.isLetterOrDigit(buffer.getValue()[i]) == false)
065                        {
066                                buffer.getValue()[i] = '-';
067                        }
068                }
069
070                buffer.append("-");
071                headElementIdPrefix = buffer.toString();
072        }
073
074        @Override
075        protected final MarkupElement onComponentTag(ComponentTag tag) throws ParseException
076        {
077                // is it a <wicket:head> tag?
078                if (tag instanceof WicketTag && ((WicketTag)tag).isHeadTag())
079                {
080                        inHead = tag.isOpen();
081                }
082                // no, it's not. Are we in <wicket:head> ?
083                else if (inHead == true)
084                {
085                        // is the tag open and has empty wicket:id?
086                        if ((tag instanceof WicketTag == false) && (tag.getId() == null) &&
087                                (tag.isOpen() || tag.isOpenClose()) && needId(tag))
088                        {
089                                if (tag.getAttributes().get("id") == null)
090                                {
091                                        tag.getAttributes().put("id", headElementIdPrefix + nextValue());
092                                        tag.setModified(true);
093                                }
094                        }
095                }
096
097                return tag;
098        }
099
100        /**
101         * 
102         * @param tag
103         * @return true, if id is needed
104         */
105        private boolean needId(final ComponentTag tag)
106        {
107                final String name = tag.getName().toLowerCase(Locale.ROOT);
108                if (name.equals("script") && tag.getAttributes().containsKey("src") == false)
109                {
110                        return true;
111                }
112                else if (name.equals("style") && tag.getAttributes().containsKey("href") == false)
113                {
114                        return true;
115                }
116
117                return false;
118        }
119
120        /**
121         * 
122         * @return The next value
123         */
124        private int nextValue()
125        {
126                return counter++;
127        }
128}