/**
   * Gets the link node of the previous page.
   *
   * @param page the pagination object
   * @param contextPath current context path of servlet
   * @return the node
   */
  private Element getPreviousElement(final Page<?> page, final String contextPath) {
    final Element result = new Element("li");

    String url;
    if (!page.hasPrevious()) {
      url = getPagedParams(contextPath, 0, page.getSize());
    } else {
      url = getPagedParams(contextPath, page.getNumber() - 1, page.getSize());
    }

    final Element a = new Element("a");
    a.setAttribute("href", url);
    result.addChild(a);

    final Element icon = new Element("i");
    icon.setAttribute("class", "glyphicon glyphicon-triangle-left");

    a.addChild(icon);

    if (!page.hasPrevious()) {
      result.setAttribute("class", "disabled");
    }

    return result;
  }
  /**
   * Gets the link node of the next page.
   *
   * @param page the pagination object
   * @param contextPath current context path of servlet
   * @return the node
   */
  private Element getNextElement(final Page<?> page, final String contextPath) {
    final Element result = new Element("li");

    String url;
    if (!page.hasNext()) {
      int pageNumber = page.getTotalPages() - 1;
      url = getPagedParams(contextPath, pageNumber < 0 ? 0 : pageNumber, page.getSize());
    } else {
      url = getPagedParams(contextPath, page.getNumber() + 1, page.getSize());
    }

    final Element a = new Element("a");
    a.setAttribute("href", url);
    result.addChild(a);

    final Element icon = new Element("i");
    icon.setAttribute("class", "glyphicon glyphicon-triangle-right");

    a.addChild(icon);

    if (!page.hasNext()) {
      result.setAttribute("class", "disabled");
    }

    return result;
  }
  /** {@inheritDoc} */
  @Override
  protected List<Node> getMarkupSubstitutes(final Arguments arguments, final Element element) {
    final List<Node> nodes = new ArrayList<>();

    final Configuration configuration = arguments.getConfiguration();

    // Obtain the attribute value
    final IWebContext context = (IWebContext) arguments.getContext();
    final String contextPath =
        StringUtils.defaultIfEmpty(
            element.getAttributeValue(SpringHrefAttrProcessor.ATTR_NAME),
            context.getServletContext().getContextPath());

    // Obtain the Thymeleaf Standard Expression parser
    final IStandardExpressionParser parser = StandardExpressions.getExpressionParser(configuration);
    // Parse the attribute value as a Thymeleaf Standard Expression
    final String pageAttrValue = element.getAttributeValue(ATTR_PAGE);
    final IStandardExpression expression =
        parser.parseExpression(configuration, arguments, pageAttrValue);
    Page<?> page = (Page<?>) expression.execute(configuration, arguments);

    final Element container = new Element("ul");
    container.setAttribute("class", "pagination");

    container.addChild(getFirstElement(page, contextPath));
    container.addChild(getPreviousElement(page, contextPath));
    addStepElements(page, contextPath, container);
    container.addChild(getNextElement(page, contextPath));
    container.addChild(getLastElement(page, contextPath));

    nodes.add(container);

    return nodes;
  }
  @Override
  protected ProcessorResult processAttribute(
      Arguments arguments, Element element, String attributeName) {

    ProductOptionValue productOptionValue =
        (ProductOptionValue)
            StandardExpressionProcessor.processExpression(
                arguments, element.getAttributeValue(attributeName));
    ProductOptionValueDTO dto = new ProductOptionValueDTO();
    dto.setOptionId(productOptionValue.getProductOption().getId());
    dto.setValueId(productOptionValue.getId());
    dto.setValueName(productOptionValue.getAttributeValue());
    if (productOptionValue.getPriceAdjustment() != null) {
      dto.setPriceAdjustment(productOptionValue.getPriceAdjustment().getAmount());
    }
    try {
      ObjectMapper mapper = new ObjectMapper();
      Writer strWriter = new StringWriter();
      mapper.writeValue(strWriter, dto);
      element.setAttribute("data-product-option-value", strWriter.toString());
      return ProcessorResult.OK;
    } catch (Exception ex) {
      LOG.error("There was a problem writing the product option value to JSON", ex);
    }

    return null;
  }
  /**
   * Gets the link node of the first page.
   *
   * @param page the pagination object
   * @param contextPath current context path of servlet
   * @return the node
   */
  private Element getFirstElement(final Page<?> page, final String contextPath) {
    final Element result = new Element("li");

    final Element a = new Element("a");
    a.setAttribute("href", getPagedParams(contextPath, 0, page.getSize()));
    result.addChild(a);

    final Element icon = new Element("i");
    icon.setAttribute("class", "glyphicon glyphicon-step-backward");
    a.addChild(icon);

    if (page.isFirst()) {
      result.setAttribute("class", "disabled");
    }

    return result;
  }
  /**
   * Adds elements for steps.
   *
   * @param page the pagination object
   * @param contextPath current context path of servlet
   * @param container the container node of the element
   */
  private void addStepElements(
      final Page<?> page, final String contextPath, final Element container) {
    for (int step = 0; step < DEFAULT_STEPS; step++) {
      // beyond total pages is not allowed
      if (page.getTotalPages() < step + 1) {
        continue;
      }

      String url;
      int stepValue;
      if ((page.getNumber() + DEFAULT_STEPS) <= page.getTotalPages()) {
        // calculate by page number
        url = getPagedParams(contextPath, page.getNumber() + step, page.getSize());
        stepValue = page.getNumber() + step + 1;
      } else if (page.getTotalPages() < DEFAULT_STEPS && page.getTotalPages() >= step + 1) {
        // between step and DEFAULT_STEPS
        url = getPagedParams(contextPath, step, page.getSize());
        stepValue = step + 1;
      } else {
        // calculate by totalPages
        url =
            getPagedParams(
                contextPath, page.getTotalPages() - DEFAULT_STEPS + step, page.getSize());
        stepValue = page.getTotalPages() - DEFAULT_STEPS + step + 1;
      }

      final Element a = new Element("a");
      a.setAttribute("href", url);
      a.addChild(new Text(Integer.toString(stepValue)));

      final Element li = new Element("li");
      li.addChild(a);
      // set active
      if (page.getNumber() + 1 == stepValue) {
        li.setAttribute("class", "active");
      }

      container.addChild(li);
    }
  }
  @Override
  protected ProcessorResult processAttribute(
      Arguments arguments, Element element, String attributeName) {
    String[] value = element.getAttributeValue(attributeName).split(":");
    String webjarName = value[0];
    String filePath = value[1];

    element.removeAttribute(attributeName);

    element.setAttribute(
        "th:" + attribute,
        String.format("@{/{path}/%s(path=${#webjars['%s'].path})}", filePath, webjarName));
    // reevaluate th:src
    element.setRecomputeProcessorsImmediately(true);

    return ProcessorResult.OK;
  }
    @Override
    public void startElement(
        final String uri, final String localName, final String qName, final Attributes attributes)
        throws SAXException {

      if (!this.xmlDeclarationComputed) {

        // SAX specification says the "getEncoding()" method in Locator2 can only
        // be called AFTER startDocument has returned and BEFORE endDocument is called.

        if (this.locator != null && this.locator instanceof Locator2) {

          final Locator2 loc = (Locator2) this.locator;

          this.xmlVersion = loc.getXMLVersion();
          this.xmlEncoding = loc.getEncoding();

          // There seems to be no way of obtaining the "standalone" property
          // from the XML declaration.

        }

        this.xmlDeclarationComputed = true;
      }

      flushBuffer();

      Integer lineNumber = null;
      if (this.locator != null) {
        lineNumber = Integer.valueOf(this.locator.getLineNumber());
      }

      final Element element = new Element(qName, this.documentName, lineNumber);

      for (int i = 0; i < attributes.getLength(); i++) {
        element.setAttribute(
            attributes.getQName(i),
            DOMUtils.unescapeXml(
                TemplatePreprocessingReader.removeEntitySubstitutions(attributes.getValue(i)),
                true));
      }

      this.elementStack.push(element);
    }