001: /*
002: * $Id: HtmlHandler.java 461655 2006-07-30 12:37:49Z jdonnerstag $ $Revision:
003: * 1.5 $ $Date: 2006-07-30 14:37:49 +0200 (Sun, 30 Jul 2006) $
004: *
005: * ==============================================================================
006: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
007: * use this file except in compliance with the License. You may obtain a copy of
008: * the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
014: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
015: * License for the specific language governing permissions and limitations under
016: * the License.
017: */
018: package wicket.markup.parser.filter;
019:
020: import java.text.ParseException;
021: import java.util.HashMap;
022: import java.util.Map;
023:
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026:
027: import wicket.markup.ComponentTag;
028: import wicket.markup.MarkupElement;
029: import wicket.markup.parser.AbstractMarkupFilter;
030: import wicket.util.collections.ArrayListStack;
031:
032: /**
033: * This is a markup inline filter. It identifies HTML specific issues which make
034: * HTML not 100% xml compliant. E.g. tags like <p> often are missing the
035: * corresponding close tag.
036: *
037: * @author Juergen Donnerstag
038: */
039: public final class HtmlHandler extends AbstractMarkupFilter {
040: /** Logging */
041: private static final Log log = LogFactory.getLog(HtmlHandler.class);
042:
043: /** Tag stack to find balancing tags */
044: final private ArrayListStack stack = new ArrayListStack();
045:
046: /** Map of simple tags. */
047: private static final Map doesNotRequireCloseTag = new HashMap();
048:
049: static {
050: // Tags which are allowed not be closed in HTML
051: doesNotRequireCloseTag.put("p", Boolean.TRUE);
052: doesNotRequireCloseTag.put("br", Boolean.TRUE);
053: doesNotRequireCloseTag.put("img", Boolean.TRUE);
054: doesNotRequireCloseTag.put("input", Boolean.TRUE);
055: doesNotRequireCloseTag.put("hr", Boolean.TRUE);
056: doesNotRequireCloseTag.put("link", Boolean.TRUE);
057: doesNotRequireCloseTag.put("meta", Boolean.TRUE);
058: }
059:
060: /**
061: * Construct.
062: */
063: public HtmlHandler() {
064: }
065:
066: /**
067: * Get the next MarkupElement from the parent MarkupFilter and handle it if
068: * the specific filter criteria are met. Depending on the filter, it may
069: * return the MarkupElement unchanged, modified or it remove by asking the
070: * parent handler for the next tag.
071: *
072: * @see wicket.markup.parser.IMarkupFilter#nextTag()
073: * @return Return the next eligible MarkupElement
074: */
075: public MarkupElement nextTag() throws ParseException {
076: // Get the next tag. If null, no more tags are available
077: final ComponentTag tag = (ComponentTag) getParent().nextTag();
078: if (tag == null) {
079: // No more tags from the markup.
080: // If there's still a non-simple tag left, it's an error
081: while (stack.size() > 0) {
082: final ComponentTag top = (ComponentTag) stack.peek();
083:
084: if (!requiresCloseTag(top.getName())) {
085: stack.pop();
086: } else {
087: throw new ParseException("Tag " + top + " at "
088: + top.getPos()
089: + " did not have a close tag", top.getPos());
090: }
091: }
092:
093: return tag;
094: }
095:
096: if (log.isDebugEnabled()) {
097: log.debug("tag: " + tag.toUserDebugString() + ", stack: "
098: + stack);
099: }
100:
101: // Check tag type
102: if (tag.isOpen()) {
103: // Push onto stack
104: stack.push(tag);
105: } else if (tag.isClose()) {
106: // Check that there is something on the stack
107: if (stack.size() > 0) {
108: // Pop the top tag off the stack
109: ComponentTag top = (ComponentTag) stack.pop();
110:
111: // If the name of the current close tag does not match the
112: // tag on the stack then we may have a mismatched close tag
113: boolean mismatch = !top.hasEqualTagName(tag);
114:
115: if (mismatch) {
116: top.setHasNoCloseTag(true);
117:
118: // Pop any simple tags off the top of the stack
119: while (mismatch && !requiresCloseTag(top.getName())) {
120: top.setHasNoCloseTag(true);
121:
122: // Pop simple tag
123: top = (ComponentTag) stack.pop();
124:
125: // Does new top of stack mismatch too?
126: mismatch = !top.hasEqualTagName(tag);
127: }
128:
129: // If adjusting for simple tags did not fix the problem,
130: // it must be a real mismatch.
131: if (mismatch) {
132: throw new ParseException("Tag "
133: + top.toUserDebugString()
134: + " has a mismatched close tag at "
135: + tag.toUserDebugString(), top.getPos());
136: }
137: }
138:
139: // Tag matches, so add pointer to matching tag
140: tag.setOpenTag(top);
141: } else {
142: throw new ParseException("Tag "
143: + tag.toUserDebugString()
144: + " does not have a matching open tag", tag
145: .getPos());
146: }
147: } else if (tag.isOpenClose()) {
148: // Tag closes itself
149: tag.setOpenTag(tag);
150: }
151:
152: return tag;
153: }
154:
155: /**
156: * Gets whether this tag does not require a closing tag.
157: *
158: * @param name
159: * The tag's name, e.g. a, br, div, etc.
160: * @return True if this tag does not require a closing tag
161: */
162: public static boolean requiresCloseTag(final String name) {
163: return doesNotRequireCloseTag.get(name) == null;
164: }
165: }
|