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.WicketParseException;
025import org.apache.wicket.markup.parser.AbstractMarkupFilter;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029
030/**
031 * This is a markup inline filter which by default is not added to the list of markup filter. It can
032 * be added by means of subclassing Application.newMarkupParser() like
033 * 
034 * <pre>
035 * Application#init() {
036 *   getMarkupSettings().setMarkupParserFactory() {
037 *      new MarkupParserFactory() {
038 *              MarkupParser newMarkupParser(final MarkupResourceStream resource) {
039 *                MarkupParser parser=super.newMarkupParser(resource);
040 *            parser.appendMarkupFilter(new HtmlProblemFinder(HtmlProblemFinder.ERR_THROW_EXCEPTION));
041 *            return parser;
042 *          }
043 *       }
044 *    }
045 * }
046 * </pre>
047 * 
048 * The purpose of the filter is to find possible HTML issues and to log a warning.
049 * 
050 * @author Juergen Donnerstag
051 */
052public final class HtmlProblemFinder extends AbstractMarkupFilter
053{
054        /** Logging */
055        private static final Logger log = LoggerFactory.getLogger(HtmlProblemFinder.class);
056
057        /** Ignore the issue detected */
058        public static final int ERR_INGORE = 3;
059
060        /** Log a warning on the issue detected */
061        public static final int ERR_LOG_WARN = 2;
062
063        /** Log an error on the issue detected */
064        public static final int ERR_LOG_ERROR = 1;
065
066        /** Throw an exception on the issue detected */
067        public static final int ERR_THROW_EXCEPTION = 0;
068
069        /** Default behavior in case of a potential HTML issue detected */
070        private final int problemEscalation;
071
072        /**
073         * Construct.
074         * 
075         * @param problemEscalation
076         *            How to escalate the issue found.
077         */
078        public HtmlProblemFinder(final int problemEscalation)
079        {
080                this.problemEscalation = problemEscalation;
081        }
082
083        @Override
084        protected final MarkupElement onComponentTag(ComponentTag tag) throws ParseException
085        {
086                // Make sure some typical and may be tricky problems are detected and
087                // logged.
088                if ("img".equals(tag.getName()) && (tag.isOpen() || tag.isOpenClose()))
089                {
090                        String src = tag.getAttributes().getString("src");
091                        if ((src != null) && (src.trim().length() == 0))
092                        {
093                                escalateWarning("Attribute 'src' should not be empty. Location: ", tag);
094                        }
095                }
096
097                // Some people are using a dot "wicket.xxx" instead of a colon
098                // "wicket:xxx"
099                for (String key : tag.getAttributes().keySet())
100                {
101                        if (key != null)
102                        {
103                                key = key.toLowerCase(Locale.ROOT);
104                                String namespaceDot = getWicketNamespace() + '.';
105                                if (key.startsWith(namespaceDot))
106                                {
107                                        escalateWarning(
108                                                String.format("You probably want '%s:xxx' rather than '%s.xxx'. Location: ", namespaceDot, namespaceDot),
109                                                        tag);
110                                }
111                        }
112                }
113
114                return tag;
115        }
116
117        /**
118         * Handle the issue. Depending the setting either log a warning, an error, throw an exception or
119         * ignore it.
120         * 
121         * @param msg
122         *            The message
123         * @param tag
124         *            The current tag
125         * @throws WicketParseException
126         */
127        private void escalateWarning(final String msg, final ComponentTag tag)
128                throws WicketParseException
129        {
130                if (problemEscalation == ERR_LOG_WARN)
131                {
132                        log.warn(msg + tag.toUserDebugString());
133                }
134                else if (problemEscalation == ERR_LOG_ERROR)
135                {
136                        log.error(msg + tag.toUserDebugString());
137                }
138                else if (problemEscalation == ERR_INGORE)
139                {
140                        // no action required
141                }
142                else
143                // if (problemEscalation == ERR_THROW_EXCEPTION)
144                {
145                        throw new WicketParseException(msg, tag);
146                }
147        }
148}