protected String getText(Arguments arguments, Element element, String attributeName) {
    Money price;

    try {
      price =
          (Money)
              StandardExpressionProcessor.processExpression(
                  arguments, element.getAttributeValue(attributeName));
    } catch (ClassCastException e) {
      Number value =
          (Number)
              StandardExpressionProcessor.processExpression(
                  arguments, element.getAttributeValue(attributeName));
      price = new Money(value.doubleValue());
    }

    if (price == null) {
      return "Not Available";
    }
    BroadleafRequestContext brc = BroadleafRequestContext.getBroadleafRequestContext();
    if (brc.getJavaLocale() != null) {
      NumberFormat format = NumberFormat.getCurrencyInstance(brc.getJavaLocale());
      if (price.getCurrency().getCurrencyCode().equals("AUD")) {
        return "☯ " + price.getAmount().toString();
      }
      format.setCurrency(price.getCurrency());
      return format.format(price.getAmount());
    } else {

      return "$ " + price.getAmount().toString();
    }
  }
  @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 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;
  }
  /**
   * 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;
  }
 @Override
 protected String getText(
     final Arguments arguments, final Element element, final String attributeName) {
   final String attributeValue = element.getAttributeValue(attributeName);
   Money price = null;
   Object result = StandardExpressionProcessor.processExpression(arguments, attributeValue);
   if (result instanceof Money) {
     price = (Money) result;
   } else if (result instanceof BigDecimal) {
     price = new Money((BigDecimal) result);
   } else if (result == null) {
     price = Money.ZERO;
   } else {
     throw new IllegalArgumentException("Input is not of type Money or BigDecimal");
   }
   HttpServletRequest curRequest =
       ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
   Locale curLocale =
       (Locale)
           WebUtils.getSessionAttribute(
               curRequest, SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME);
   curLocale = (curLocale == null) ? Locale.US : curLocale;
   NumberFormat format = NumberFormat.getCurrencyInstance(curLocale);
   format.setCurrency(price.getCurrency());
   return format.format(price.getAmount());
 }
  @Override
  public final ProcessorResult processAttribute(
      final Arguments arguments, final Element element, final String attributeName) {

    final String attributeValue = element.getAttributeValue(attributeName);

    final RenderingEngine renderingEngine = Components.getComponent(RenderingEngine.class);
    final RenderingContext renderingContext = renderingEngine.getRenderingContext();

    AreaDefinition areaDef = null;
    BlossomTemplateDefinition templateDefinition;
    try {

      templateDefinition = (BlossomTemplateDefinition) renderingContext.getRenderableDefinition();
      if (templateDefinition.getAreas().containsKey(attributeValue)) {
        areaDef = templateDefinition.getAreas().get(attributeValue);
      }

    } catch (ClassCastException x) {

      throw new TemplateProcessingException("Only Blossom, templates supported", x);
    }

    if (areaDef == null) {
      throw new TemplateProcessingException("Area not found:" + attributeValue);
    }

    AreaElement areaElement = createElement(renderingContext);
    areaElement.setName(areaDef.getName());
    processElement(element, attributeName, areaElement);

    return ProcessorResult.OK;
  }
  @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;
  }
  /** {@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
    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);
    }
  /**
   * 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;
  }
  @Override
  protected ProcessorResult processColumnAttribute(
      Arguments arguments,
      Element element,
      String attributeName,
      HtmlTable table,
      Map<Configuration, Object> stagingConf) {

    // Get attribute value
    Boolean attrValue =
        Utils.parseElementAttribute(
            arguments, element.getAttributeValue(attributeName), false, Boolean.class);

    stagingConf.put(Configuration.COLUMN_SORTABLE, attrValue);

    return ProcessorResult.ok();
  }
  @Override
  protected ProcessorResult doProcessAttribute(
      Arguments arguments,
      Element element,
      String attributeName,
      HtmlTable table,
      Map<Configuration, Object> localConf) {

    // Get attribute value
    String attrValue =
        Utils.parseElementAttribute(
            arguments, element.getAttributeValue(attributeName), null, String.class);

    localConf.put(Configuration.AJAX_SERVERMETHOD, attrValue);

    return ProcessorResult.ok();
  }
  @Override
  protected ProcessorResult doProcessAttribute(
      Arguments arguments,
      Element element,
      String attributeName,
      HtmlTable table,
      Map<Configuration, Object> localConf) {

    // Get attribute value
    String attrValue =
        Utils.parseElementAttribute(
            arguments, element.getAttributeValue(attributeName), null, String.class);

    // Override default value with the attribute's one
    if (table != null) {
      table.getLastHeaderRow().getLastColumn().setSortInit(attrValue);
    }

    return ProcessorResult.ok();
  }
  /**
   * 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
  @SuppressWarnings("unchecked")
  protected Map<String, String> getModifiedAttributeValues(
      Arguments arguments, Element element, String attributeName) {
    Map<String, String> attrs = new HashMap<String, String>();

    BroadleafRequestContext blcContext = BroadleafRequestContext.getBroadleafRequestContext();
    HttpServletRequest request = blcContext.getRequest();

    String baseUrl = request.getRequestURL().toString();
    Map<String, String[]> params = new HashMap<String, String[]>(request.getParameterMap());

    String key = ProductSearchCriteria.SORT_STRING;
    String sortField = element.getAttributeValue(attributeName);

    List<String[]> sortedFields = new ArrayList<String[]>();

    String[] paramValues = params.get(key);
    if (paramValues != null && paramValues.length > 0) {
      String sortQueries = paramValues[0];
      for (String sortQuery : sortQueries.split(",")) {
        String[] sort = sortQuery.split(" ");
        if (sort.length == 2) {
          sortedFields.add(new String[] {sort[0], sort[1]});
        }
      }
    }

    boolean currentlySortingOnThisField = false;
    boolean currentlyAscendingOnThisField = false;

    for (String[] sortedField : sortedFields) {
      if (sortField.equals(sortedField[0])) {
        currentlySortingOnThisField = true;
        currentlyAscendingOnThisField = sortedField[1].equals("asc");
        sortedField[1] = currentlyAscendingOnThisField ? "desc" : "asc";
      }
    }

    String sortString = sortField;
    String classString = "";

    if (currentlySortingOnThisField) {
      classString += "active ";
      if (currentlyAscendingOnThisField) {
        sortString += " desc";
        classString += "asc ";
      } else {
        sortString += " asc";
        classString += "desc ";
      }
    } else {
      sortString += " asc";
      classString += "asc ";
      params.remove(ProductSearchCriteria.PAGE_NUMBER);
    }

    if (allowMultipleSorts) {
      StringBuilder sortSb = new StringBuilder();
      for (String[] sortedField : sortedFields) {
        sortSb.append(sortedField[0]).append(" ").append(sortedField[1]).append(",");
      }

      sortString = sortSb.toString();
      if (sortString.charAt(sortString.length() - 1) == ',') {
        sortString = sortString.substring(0, sortString.length() - 1);
      }
    }

    params.put(key, new String[] {sortString});

    String url = ProcessorUtils.getUrl(baseUrl, params);

    attrs.put("class", classString);
    attrs.put("href", url);
    return attrs;
  }