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}