nl.vpro.jcr.criteria.query.xpath.utils.XPathTextUtils.java Source code

Java tutorial

Introduction

Here is the source code for nl.vpro.jcr.criteria.query.xpath.utils.XPathTextUtils.java

Source

/**
 *
 * Criteria API for Magnolia CMS (http://www.openmindlab.com/lab/products/mgnlcriteria.html)
 * Copyright(C) 2009-2013, Openmind S.r.l. http://www.openmindonline.it
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package nl.vpro.jcr.criteria.query.xpath.utils;

import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;

import nl.vpro.jcr.criteria.query.criterion.Criterion;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A utility class to escape xpath strings
 * @author fgiust
 * @author fgrilli
 */
public final class XPathTextUtils {

    private static final Logger LOG = LoggerFactory.getLogger(XPathTextUtils.class);

    /**
     * Date format used for date formatting.
     */
    private static final FastDateFormat DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZ",
            TimeZone.getDefault(), Locale.ENGLISH);

    private XPathTextUtils() {
    }

    /**
     * Convert a string to an XPath 2.0 string literal, suitable for inclusion in a query. See JSR-170 spec v1.0, Sec.
     * 6.6.4.9.
     * @param str Any string.
     * @return A valid XPath 2.0 string literal, including enclosing quotes.
     */
    public static String stringToXPathLiteral(String str) {
        if (StringUtils.isEmpty(str)) {
            return str;
        }
        // Single quotes needed for jcr:contains()
        return str.replaceAll("'", "\"");
    }

    /**
     * Convert a string to a JCR search expression literal, suitable for use in jcr:contains() (inside XPath queries).
     * The characters - and " have special meaning, and may be escaped with a backslash to obtain their literal value.
     * See JSR-170 spec v1.0, Sec. 6.6.5.2.
     * @param str Any string.
     * @return A valid XPath 2.0 string literal suitable for use in jcr:contains(), including enclosing quotes.
     */
    public static String stringToJCRSearchExp(String str) {
        if (StringUtils.isEmpty(str)) {
            return str;
        }

        String parseString = StringUtils.trimToEmpty(str);

        // workaround for https://issues.apache.org/jira/browse/JCR-2732
        parseString = StringUtils.replaceEach(parseString, new String[] { ":)", ":(" },
                new String[] { ": )", ": (" });

        /*
         * http://lucene.apache.org/java/2_4_0/queryparsersyntax.html#Escaping%20Special%20Characters
         * http://www.javalobby.org/java/forums/t86124.html
         */
        String escapeChars = "[\\\\+\\-\\!\\(\\)\\:\\^\\]\\{\\}\\~\\*\\?\"\\[\\]|]";
        parseString = parseString.replaceAll(escapeChars, "\\\\$0");
        parseString = parseString.replaceAll("\'", "\'\'");

        // workaround for https://issues.apache.org/jira/browse/JCR-2733
        if (StringUtils.startsWith(parseString, "OR ")) {
            parseString = parseString.replaceFirst("\\bOR\\b", "\"OR\"");
        }

        return parseString;

    }

    /**
     * @param path to encode eg //my//path/2009//*
     * @return String encoded path eg //my//path/_x0032_009//*
     */
    public static String encodeDigitsInPath(String path) {
        LOG.debug("path to encode is {}", path);
        if (StringUtils.isBlank(path)) {
            String msg = "path cannot be a null or empty string";
            LOG.error(msg);
            throw new IllegalArgumentException(msg);
        }

        StringBuilder encodedPath = new StringBuilder(path.length());

        int inXpathCondition = 0;
        boolean xpathWithFunction = false;

        // TODO maybe a more robust check is needed
        for (int i = 0; i < path.length(); ++i) {
            char ch = path.charAt(i);

            if (i > 0 && path.charAt(i - 1) == '/' && Character.isDigit(ch)) {
                encodedPath.append("_x").append(StringUtils.leftPad(Integer.toHexString(ch), 4, '0')).append("_");
            } else if (i > 0 && path.charAt(i - 1) == '/' && ch == '-') {
                encodedPath.append("_x002d_");
            } else if (inXpathCondition <= 0 && ch == ' ') {
                encodedPath.append("_x0020_");
            } else if (inXpathCondition <= 0 && ch == ',') {
                encodedPath.append("_x002c_");
            } else if (inXpathCondition <= 0 && ch == '\u00b0') { // CRIT-53
                encodedPath.append("_x00b0_");
            } else if (inXpathCondition <= 0 && ch == '$') { // CRIT-54
                encodedPath.append("_x0024_");
            } else {
                if (ch == '[') {
                    inXpathCondition++;
                } else if (ch == '(') {
                    // "(" is the beginning of an expression only when used with the element() function
                    if (StringUtils.endsWith(StringUtils.substring(path, 0, i), "element")) {
                        inXpathCondition++;
                        xpathWithFunction = true;
                    } else if (inXpathCondition == 0) {
                        encodedPath.append("_x0028_");
                        continue;
                    }
                } else if (inXpathCondition > 0 && ch == ']') {
                    inXpathCondition--;
                } else if (ch == ')') {
                    if (inXpathCondition > 0 && xpathWithFunction) {
                        inXpathCondition--;
                        xpathWithFunction = false;
                    } else if (inXpathCondition == 0) {
                        encodedPath.append("_x0029_");
                        continue;
                    }
                }
                encodedPath.append(ch);
            }
        }
        LOG.debug("returning encoded path {}", encodedPath);
        return encodedPath.toString();
    }

    /**
     * Get a date in the XSD format "yyyy-MM-ddThh:mm:ss.SSS:+01:00"
     * @param date input calendar
     * @return XSD formatted date
     */
    public static String toXsdDate(Calendar date) {
        if (date == null) {
            return null;
        }

        String xsdDate = DATE_FORMAT.format(date);
        int length = xsdDate.length();

        return StringUtils.substring(xsdDate, 0, length - 2) + ":"
                + StringUtils.substring(xsdDate, length - 2, length);
    }

    public static String toXPath(String path) {
        if (!isValidNodePath(path)) {
            throw new IllegalArgumentException("Path " + path + " is not a valid JCR node path");
        }

        if (path.equals("/")) {
            return Criterion.ALL_ELEMENTS;
        }

        return encodeDigitsInPath(Criterion.JCR_ROOT + StringUtils.removeEnd(path, "/") + "//*");
    }

    public static boolean isValidNodePath(String path) {
        return path != null && path.startsWith("/") && !path.contains("//") && !path.contains("*")
                && !path.contains("(") && !path.contains(")");
    }
}