SPARQLQueries.java

/*******************************************************************************
 * Copyright (c) 2021 Eclipse RDF4J contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *******************************************************************************/
package org.eclipse.rdf4j.query.parser.sparql;

import org.eclipse.rdf4j.model.Namespace;

/**
 * Utility functions for working with SPARQL query strings.
 *
 * @author Jeen Broekstra
 */
public class SPARQLQueries {

	/**
	 * Creates a string representing of the supplied {@link Namespace namespaces} as SPARQL prefix declarations. This
	 * can be used when composing a SPARQL query string in code, for example:
	 *
	 * <pre>
	 * <code>
	 * String query = SPARQLQueries.getPrefixClauses(connection.getNamespaces()) + "SELECT * WHERE { ?s ex:myprop ?o }";
	 * </code>
	 * </pre>
	 *
	 * @param namespaces one or more {@link Namespace} objects.
	 * @return one or more SPARQL prefix declarations (each separated by a newline), as a String.
	 * @since 3.6.0
	 */
	public static String getPrefixClauses(Iterable<Namespace> namespaces) {
		final StringBuilder sb = new StringBuilder();
		for (Namespace namespace : namespaces) {
			sb.append(String.format("PREFIX %s: <%s>\n", namespace.getPrefix(), namespace.getName()));
		}
		return sb.toString();
	}

	/**
	 * Escape the supplied string with backslashes for any special characters, so it can be used as a string literal
	 * value in a SPARQL query.
	 *
	 * @see <a href="https://www.w3.org/TR/sparql11-query/#grammarEscapes">SPAQL 1.1 grammar escapes</a>
	 * @since 3.6.0
	 */
	public static String escape(String s) {
		s = s.replace("\\", "\\\\");
		s = s.replace("\t", "\\t");
		s = s.replace("\n", "\\n");
		s = s.replace("\r", "\\r");
		s = s.replace("\b", "\\b");
		s = s.replace("\f", "\\f");
		s = s.replace("\"", "\\\"");
		s = s.replace("'", "\\'");
		return s;
	}

	/**
	 * Un-escapes a backslash-escaped SPARQL literal value string. Any recognized \-escape sequences are substituted
	 * with their un-escaped value.
	 *
	 * @param s An SPARQL literal string with backslash escapes.
	 * @return The un-escaped string.
	 * @throws IllegalArgumentException If the supplied string is not a correctly escaped SPARQL string.
	 * @see <a href="https://www.w3.org/TR/sparql11-query/#grammarEscapes">SPAQL 1.1 grammar escapes</a>
	 * @since 3.6.0
	 */
	public static String unescape(String s) {
		int backSlashIdx = s.indexOf('\\');

		if (backSlashIdx == -1) {
			// No escaped characters found
			return s;
		}

		int startIdx = 0;
		int sLength = s.length();
		StringBuilder sb = new StringBuilder(sLength);

		while (backSlashIdx != -1) {
			sb.append(s, startIdx, backSlashIdx);

			if (backSlashIdx + 1 >= sLength) {
				throw new IllegalArgumentException("Unescaped backslash in: " + s);
			}

			char c = s.charAt(backSlashIdx + 1);
			switch (c) {
			case 't':
				sb.append('\t');
				startIdx = backSlashIdx + 2;
				break;
			case 'n':
				sb.append('\n');
				startIdx = backSlashIdx + 2;
				break;
			case 'r':
				sb.append('\r');
				startIdx = backSlashIdx + 2;
				break;
			case 'b':
				sb.append('\b');
				startIdx = backSlashIdx + 2;
				break;
			case 'f':
				sb.append('\f');
				startIdx = backSlashIdx + 2;
				break;
			case '"':
				sb.append('"');
				startIdx = backSlashIdx + 2;
				break;
			case '\'':
				sb.append('\'');
				startIdx = backSlashIdx + 2;
				break;
			case '\\':
				sb.append('\\');
				startIdx = backSlashIdx + 2;
				break;
			default:
				throw new IllegalArgumentException("Unescaped backslash in: " + s);
			}

			backSlashIdx = s.indexOf('\\', startIdx);
		}

		sb.append(s.substring(startIdx));

		return sb.toString();
	}
}