All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.wicket.protocol.http.documentvalidation.HtmlDocumentValidator Maven / Gradle / Ivy

Go to download

Wicket is a Java web application framework that takes simplicity, separation of concerns and ease of development to a whole new level. Wicket pages can be mocked up, previewed and later revised using standard WYSIWYG HTML design tools. Dynamic content processing and form handling is all handled in Java code using a first-class component model backed by POJO data beans that can easily be persisted using your favorite technology.

There is a newer version: 10.0.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.wicket.protocol.http.documentvalidation;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.wicket.util.collections.ArrayListStack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Simple class that provides a convenient programmatic way to define what an expected HTML document
 * should look like and then to validate a supplied document against this template. Note that this
 * validator expects very clean HTML (which should not be a problem during testing). In particular
 * it expects tags to be matched and that the following tags with optional close tags are actually
 * closed: p, td, th, li and option.
 * 
 * @author Chris Turner
 */
public class HtmlDocumentValidator
{
	private static final Logger log = LoggerFactory.getLogger(HtmlDocumentValidator.class);

	private final List elements = new ArrayList();

	private boolean skipComments = true;

	private Tag workingTag;

	/**
	 * Create the validator.
	 */
	public HtmlDocumentValidator()
	{
	}

	/**
	 * Add a root element to the validator. This will generally be the HTML element to which all
	 * children are added. However, it may also be other elements to represent comments or similar.
	 * 
	 * @param e
	 *            The element to add
	 */
	public void addRootElement(final DocumentElement e)
	{
		elements.add(e);
	}

	/**
	 * Check whether the supplied document is valid against the spec that has been built up within
	 * the validator.
	 * 
	 * @param document
	 *            The document to validate
	 * @return Whether the document is valid or not
	 */
	public boolean isDocumentValid(final String document)
	{
		HtmlDocumentParser parser = new HtmlDocumentParser(document);
		Iterator expectedElements = elements.iterator();
		ArrayListStack> iteratorStack = new ArrayListStack>();
		ArrayListStack tagNameStack = new ArrayListStack();

		boolean end = false;
		boolean valid = true;
		while (!end)
		{
			int token = parser.getNextToken();
			switch (token)
			{
				case HtmlDocumentParser.UNKNOWN :
					// Error is already recorded by the parser
					return false;
				case HtmlDocumentParser.END :
					end = true;
					break;
				case HtmlDocumentParser.COMMENT :
					valid = validateComment(expectedElements, parser);
					if (!valid)
					{
						end = true;
					}
					break;
				case HtmlDocumentParser.OPEN_TAG :
					valid = validateTag(expectedElements, parser);
					if (!valid)
					{
						end = true;
					}
					else
					{
						expectedElements = saveOpenTagState(iteratorStack, expectedElements,
							tagNameStack);
					}
					break;
				case HtmlDocumentParser.OPENCLOSE_TAG :
					valid = validateTag(expectedElements, parser);
					if (valid)
					{
						valid = checkOpenCloseTag();
					}
					if (!valid)
					{
						end = true;
					}
					break;
				case HtmlDocumentParser.CLOSE_TAG :
					expectedElements = validateCloseTag(tagNameStack, parser, expectedElements,
						iteratorStack);
					if (expectedElements == null)
					{
						valid = false;
						end = true;
					}
					break;
				case HtmlDocumentParser.TEXT :
					valid = validateText(expectedElements, parser);
					if (!valid)
					{
						end = true;
					}
					break;
			}
		}

		// Return the valid result
		return valid;
	}

	/**
	 * Set whether to skip comments of not when validating. The default is true. If this is set to
	 * false then Comment elements must be added to represent each comment to be validated.
	 * 
	 * @param skipComments
	 *            Whether to skip comments or not
	 */
	public void setSkipComments(final boolean skipComments)
	{
		this.skipComments = skipComments;
	}

	/**
	 * Check whether the open close tag was actually expected to have children.
	 * 
	 * @return Whether valid or not
	 */
	private boolean checkOpenCloseTag()
	{
		boolean valid = true;
		if (!workingTag.getExpectedChildren().isEmpty())
		{
			log.error("Found tag <" + workingTag.getTag() + "/> was expected to have " +
				workingTag.getExpectedChildren().size() + " child elements");
			valid = false;
		}
		return valid;
	}

	/**
	 * Check if the supplied tag is one that expects to be closed or not.
	 * 
	 * @param tag
	 *            The tag
	 * @return Whether the tag requires closing or not
	 */
	private boolean isNonClosedTag(String tag)
	{
		tag = workingTag.getTag().toLowerCase();
		if (tag.equals("area"))
		{
			return true;
		}
		if (tag.equals("base"))
		{
			return true;
		}
		if (tag.equals("basefont"))
		{
			return true;
		}
		if (tag.equals("bgsound"))
		{
			return true;
		}
		if (tag.equals("br"))
		{
			return true;
		}
		if (tag.equals("col"))
		{
			return true;
		}
		if (tag.equals("frame"))
		{
			return true;
		}
		if (tag.equals("hr"))
		{
			return true;
		}
		if (tag.equals("img"))
		{
			return true;
		}
		if (tag.equals("input"))
		{
			return true;
		}
		if (tag.equals("isindex"))
		{
			return true;
		}
		if (tag.equals("keygen"))
		{
			return true;
		}
		if (tag.equals("link"))
		{
			return true;
		}
		if (tag.equals("meta"))
		{
			return true;
		}
		if (tag.equals("param"))
		{
			return true;
		}
		if (tag.equals("spacer"))
		{
			return true;
		}
		if (tag.equals("wbr"))
		{
			return true;
		}
		return false;
	}

	/**
	 * Save the new open tag state and find the iterator to continue to use for processing.
	 * 
	 * @param iteratorStack
	 *            The current stack of iterators
	 * @param expectedElements
	 *            The current iterator of elements
	 * @param tagNameStack
	 *            The stack of open tags
	 * @return The iterator to continue to use
	 */
	private Iterator saveOpenTagState(
		ArrayListStack> iteratorStack,
		Iterator expectedElements, ArrayListStack tagNameStack)
	{
		if (!isNonClosedTag(workingTag.getTag()))
		{
			iteratorStack.push(expectedElements);
			expectedElements = workingTag.getExpectedChildren().iterator();
			tagNameStack.push(workingTag.getTag());
		}
		return expectedElements;
	}

	/**
	 * Validate the close tag that was found.
	 * 
	 * @param tagNameStack
	 *            The stack of tag names
	 * @param parser
	 *            The parser
	 * @param expectedElements
	 *            The current iterator of expected elements
	 * @param iteratorStack
	 *            The stack of previous iterators
	 * @return The next iterator to use, or null
	 */
	private Iterator validateCloseTag(ArrayListStack tagNameStack,
		HtmlDocumentParser parser, Iterator expectedElements,
		ArrayListStack> iteratorStack)
	{
		if (tagNameStack.isEmpty())
		{
			log.error("Found closing tag  when there are no " +
				"tags currently open");
			expectedElements = null;
		}
		else
		{
			String expectedTag = tagNameStack.pop();
			if (!expectedTag.equals(parser.getTag()))
			{
				log.error("Found closing tag  when we expecting " +
					"the closing tag  instead");
				expectedElements = null;
			}
			else
			{
				if (expectedElements.hasNext())
				{
					DocumentElement e = expectedElements.next();
					log.error("Found closing tag  but we were " +
						"expecting to find another child element: " + e.toString());
					expectedElements = null;
				}
				else
				{
					if (iteratorStack.isEmpty())
					{
						log.error("Unexpected parsing error");
						expectedElements = null;
					}
					else
					{
						expectedElements = iteratorStack.pop();
					}
				}
			}
		}
		return expectedElements;
	}

	/**
	 * Validate the comment token that was found.
	 * 
	 * @param expectedElements
	 *            The iterator of expected elements
	 * @param parser
	 *            The parser
	 * @return Whether the comment is valid or not
	 */
	private boolean validateComment(Iterator expectedElements,
		HtmlDocumentParser parser)
	{
		boolean valid = true;
		if (!skipComments)
		{
			if (expectedElements.hasNext())
			{
				DocumentElement e = expectedElements.next();
				if (e instanceof Comment)
				{
					if (!((Comment)e).getText().equals(parser.getComment()))
					{
						log.error("Found comment '" + parser.getComment() + "' does not match " +
							"expected comment '" + ((Comment)e).getText() + "'");
						valid = false;
					}
				}
				else
				{
					log.error("Found comment '" + parser.getComment() + "' was not expected. " +
						"We were expecting: " + e.toString());
					valid = false;
				}
			}
			else
			{
				log.error("Found comment '" + parser.getComment() + "' was not expected. " +
					"We were not expecting any more elements within the current tag");
				valid = false;
			}
		}
		return valid;
	}

	/**
	 * Validate the tag token that was found.
	 * 
	 * @param expectedElements
	 *            The iterator of expected elements
	 * @param parser
	 *            The parser
	 * @return Whether the tag is valid or not
	 */
	private boolean validateTag(Iterator expectedElements,
		HtmlDocumentParser parser)
	{
		boolean valid = true;
		if (expectedElements.hasNext())
		{
			DocumentElement e = expectedElements.next();
			if (e instanceof Tag)
			{
				workingTag = (Tag)e;
				if (!workingTag.getTag().equals(parser.getTag()))
				{
					log.error("Found tag <" + parser.getTag() + "> does not match " +
						"expected tag <" + workingTag.getTag() + ">");
					valid = false;
				}
				else
				{
					Map actualAttributes = parser.getAttributes();

					Map expectedAttributes = workingTag.getExpectedAttributes();
					for (Map.Entry entry : expectedAttributes.entrySet())
					{
						String name = entry.getKey();
						String pattern = entry.getValue();
						if (!actualAttributes.containsKey(name))
						{
							log.error("Tag <" + workingTag.getTag() + "> was expected to have a '" +
								name + "' attribute " + "but this was not present");
							valid = false;
						}

						String value = actualAttributes.get(name);
						if (value == null)
						{
							log.error("Attribute " + name + " was expected but not found");
							valid = false;
						}
						else
						{
							if (!value.matches(pattern))
							{
								log.error("The value '" + value + "' of attribute '" + name +
									"' of tag <" + workingTag.getTag() +
									"> was expected to match the pattern '" + pattern +
									"' but it does not");
								valid = false;
							}
						}
					}

					for (String name : workingTag.getIllegalAttributes())
					{
						if (actualAttributes.containsKey(name))
						{
							log.error("Tag <" + workingTag.getTag() +
								"> should not have an attributed named '" + name + "'");
							valid = false;
						}
					}
				}
			}
			else
			{
				log.error("Found tag <" + parser.getTag() + "> was not expected. " +
					"We were expecting: " + e.toString());
				valid = false;
			}
		}
		else
		{
			log.error("Found tag <" + parser.getTag() + "> was not expected. " +
				"We were not expecting any more elements within the current tag");
			valid = false;
		}
		return valid;
	}

	/**
	 * Validate the text token that was found.
	 * 
	 * @param expectedElements
	 *            The iterator of expected elements
	 * @param parser
	 *            The parser
	 * @return Whether the text is valid or not
	 */
	private boolean validateText(Iterator expectedElements,
		HtmlDocumentParser parser)
	{
		boolean valid = true;
		if (expectedElements.hasNext())
		{
			DocumentElement e = expectedElements.next();
			if (e instanceof TextContent)
			{
				if (!parser.getText().matches(((TextContent)e).getValue()))
				{
					log.error("Found text '" + parser.getText() + "' does not match " +
						"expected text '" + ((TextContent)e).getValue() + "'");
					valid = false;
				}
			}
			else
			{
				log.error("Found text '" + parser.getText() + "' was not expected. " +
					"We were expecting: " + e.toString());
				valid = false;
			}
		}
		else
		{
			log.error("Found text '" + parser.getText() + "' was not expected. " +
				"We were not expecting any more elements within the current tag");
			valid = false;
		}
		return valid;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy