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.Arrays; 021import java.util.List; 022import java.util.Locale; 023 024import org.apache.wicket.markup.ComponentTag; 025import org.apache.wicket.markup.MarkupElement; 026import org.apache.wicket.markup.WicketTag; 027import org.apache.wicket.markup.parser.AbstractMarkupFilter; 028import org.apache.wicket.markup.parser.XmlTag.TagType; 029import org.apache.wicket.markup.resolver.HtmlHeaderResolver; 030 031/** 032 * MarkupFilter that expands certain open-close tag as separate open and close tags. Firefox, unless 033 * it gets text/xml mime type, treats these open-close tags as open tags which results in corrupted 034 * DOM. This happens even with xhtml doctype. 035 * 036 * In addition, some tags are required open-body-close for Wicket to work properly. 037 * 038 * @author Juergen Donnerstag 039 * @author Matej Knopp 040 */ 041public class OpenCloseTagExpander extends AbstractMarkupFilter 042{ 043 // A list of elements which should not be expanded from TagType.OPEN_CLOSE to TagType.OPEN + TagType.CLOSE 044 // http://www.w3.org/TR/html-markup/syntax.html#void-element 045 // area, base, br, col, command, embed, hr, img, input, keygen, link, meta, param, source, track, wbr 046 047 static final List<String> REPLACE_FOR_TAGS = Arrays.asList("a", "q", "sub", "sup", 048 "abbr", "acronym", "cite", "code", "del", "dfn", "em", "ins", "kbd", "samp", "var", 049 "label", "textarea", "tr", "td", "th", "caption", "thead", "tbody", "tfoot", "dl", "dt", 050 "dd", "li", "ol", "ul", "h1", "h2", "h3", "h4", "h5", "h6", "i", 051 "pre", 052 "title", 053 "div", 054 055 // tags from pre 1.5 days, shouldn't really be here but make this release more backwards 056 // compatible 057 "span", "p", 058 "strong", 059 "b", 060 "e", 061 "select", 062 063 // @TODO by now an exclude list is probably shorter 064 "article", "aside", "details", "summary", "figure", "figcaption", "footer", 065 "header", "hgroup", "mark", "meter", "nav", "progress", "ruby", "rt", "rp", "section", 066 "audio", "video", "canvas", "datalist", "output", HtmlHeaderResolver.HEADER_ITEMS); 067 068 // temporary storage. Introduce into flow on next request 069 private ComponentTag next = null; 070 071 @Override 072 public MarkupElement nextElement() throws ParseException 073 { 074 // Did we hold back an elem? Than return that first 075 if (next != null) 076 { 077 MarkupElement rtn = next; 078 next = null; 079 return rtn; 080 } 081 082 return super.nextElement(); 083 } 084 085 @Override 086 protected MarkupElement onComponentTag(final ComponentTag tag) throws ParseException 087 { 088 if (tag.isOpenClose()) 089 { 090 String name = tag.getName(); 091 092 if (contains(name)) 093 { 094 if (onFound(tag)) 095 { 096 next = new ComponentTag(tag.getName(), TagType.CLOSE); 097 if (getWicketNamespace().equals(tag.getNamespace())) 098 { 099 next = new WicketTag(next); 100 } 101 next.setNamespace(tag.getNamespace()); 102 next.setOpenTag(tag); 103 next.setModified(true); 104 } 105 } 106 } 107 108 return tag; 109 } 110 111 /** 112 * Can be subclassed to do other things. E.g. instead of changing it you may simply want to log 113 * a warning. 114 * 115 * @param tag 116 * @return Must be true to automatically create and add a close tag. 117 */ 118 protected boolean onFound(final ComponentTag tag) 119 { 120 tag.setType(TagType.OPEN); 121 tag.setModified(true); 122 123 return true; 124 } 125 126 /** 127 * Allows subclasses to easily expand the list of tag which needs to be expanded. 128 * 129 * @param name 130 * @return true, if needs expansion 131 */ 132 protected boolean contains(final String name) 133 { 134 return REPLACE_FOR_TAGS.contains(name.toLowerCase(Locale.ROOT)); 135 } 136}