Package org.apache.wicket.protocol.http.documentvalidation

Source Code of org.apache.wicket.protocol.http.documentvalidation.HtmlDocumentValidator

/*
* 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 </" + parser.getTag() + "> when there are no " +
          "tags currently open");
      expectedElements = null;
    }
    else
    {
      String expectedTag = (String)tagNameStack.pop();
      if (!expectedTag.equals(parser.getTag()))
      {
        log.error("Found closing tag </" + parser.getTag() + "> when we expecting " +
            "the closing tag </" + expectedTag + "> instead");
        expectedElements = null;
      }
      else
      {
        if (expectedElements.hasNext())
        {
          DocumentElement e = (DocumentElement)expectedElements.next();
          log.error("Found closing tag </" + parser.getTag() + "> 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 = (Iterator)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 = (DocumentElement)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 = (DocumentElement)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 (Iterator it = expectedAttributes.entrySet().iterator(); it.hasNext();)
          {
            Map.Entry entry = (Map.Entry) it.next();
            String name = (String) entry.getKey();
            String pattern = (String)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 = (String)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 (Iterator it = workingTag.getIllegalAttributes().iterator(); it.hasNext();)
          {
            String name = (String)it.next();
            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 = (DocumentElement)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;
  }

}
TOP

Related Classes of org.apache.wicket.protocol.http.documentvalidation.HtmlDocumentValidator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.