All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.daisy.pipeline.css.impl.StylesheetParametersParser Maven / Gradle / Ivy

The newest version!
package org.daisy.pipeline.css.impl;

import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.common.collect.Iterables;

import org.daisy.pipeline.css.sass.impl.SassMapParser;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.unbescape.css.CssEscape;

public abstract class StylesheetParametersParser {

	private static final Logger logger = LoggerFactory.getLogger(StylesheetParametersParser.class);
	private static final Pattern regex = Pattern.compile(
		(SassMapParser.asRegex().pattern() + "|" + Query.QUERY.pattern()).replaceAll("\\(\\?<[^>]+>", "(?:"));

	private StylesheetParametersParser() {}

	public static Map parse(String expression) throws IllegalArgumentException {
		Map map = null;
		try {
			map = SassMapParser.parse(expression);
		} catch (IllegalArgumentException e) {
			Query query = null; {
				try {
					query = Query.parse(expression);
					logger.warn(
						"Query syntax is deprecated for stylesheet-parameters. Please use Sass map syntax.");
				} catch (IllegalArgumentException ee) {
				}
			}
			if (query != null) {
				map = new HashMap<>();
				for (Query.Feature f : query)
					map.put(f.getKey(), f.getValue().orElse("true"));
			}
		}
		if (map == null)
			throw new IllegalArgumentException("Could not parse stylesheet parameter set: " + expression);
		return map;
	}

	public static Pattern asRegex() {
		return regex;
	}

	/*
	 * The code below is copied from the Query class in braille-common. The class is not referenced
	 * because css-utils is a dependency of braille-common. For now we chose to copy rather than to
	 * move the class because the query syntax was introduced as a way to select braille
	 * transformers. Later we started using it to parse "stylesheet-parameter" options also, but
	 * only because it was convenient. Now we have a different syntax for stylesheet parameters,
	 * namely the Sass map syntax. For backward compatibility the query syntax is also still supported.
	 */
	private static class Query extends AbstractCollection {

		private static final String IDENT_RE = "[_a-zA-Z][_a-zA-Z0-9-]*";
		private static final String STRING_RE = "'[^']*'|\"[^\"]*\"";
		private static final String INTEGER_RE = "0|-?[1-9][0-9]*";
		private static final Pattern VALUE_RE = Pattern.compile(
			"(?" + IDENT_RE + ")|(?" + STRING_RE + ")|(?" + INTEGER_RE + ")"
		);
		private static final Pattern FEATURE_RE = Pattern.compile(
			"\\(\\s*(?" + IDENT_RE + ")(?:\\s*\\:\\s*(?" + VALUE_RE.pattern() + "))?\\s*\\)"
		);
		private static final Pattern FEATURES_RE = Pattern.compile(
			"\\s*(?:" + FEATURE_RE.pattern() + "\\s*)*"
		);
		private static final Pattern QUERY = FEATURES_RE;

		/**
		 * Parse string to query
		 */
		public static Query parse(String query) throws IllegalArgumentException {
			if (FEATURES_RE.matcher(query).matches()) {
				Query q = new Query();
				Matcher m = FEATURE_RE.matcher(query);
				while (m.find()) {
					String key = m.group("key");
					String value = m.group("value");
					boolean isString = false;
					if (value != null) {
						Matcher m2 = VALUE_RE.matcher(value);
						if (!m2.matches())
							throw new RuntimeException("Coding error");
						String ident = m2.group("ident");
						String string = m2.group("string");
						String integer = m2.group("integer");
						if (ident != null)
							value = ident;
						else if (string != null && !string.equals("")) {
							value = CssEscape.unescapeCss(string.substring(1, string.length() - 1));
							isString = true; }
						else if (integer != null && !integer.equals(""))
							value = integer;
						else
							throw new RuntimeException("Coding error"); }
					q.add(new Feature(key, Optional.ofNullable(value), isString)); }
				return q; }
			throw new IllegalArgumentException("Could not parse query: " + query);
		}

		private final List list;

		private Query() {
			this.list = new ArrayList();
		}

		@Override
		public boolean add(Feature feature) {
			int index = 0;
			String key = feature.getKey();
			for (Feature f : list) {
				int cmp = f.getKey().compareTo(key);
				if (cmp > 0) {
					break;
				} else if (cmp > 0) {
					index++;
					break;
				}
				index++;
			}
			list.add(index, feature);
			return true;
		}

		@Override
		public int size() {
			return list.size();
		}

		@Override
		public Iterator iterator() {
			return list.iterator();
		}

		@Override
		public String toString() {
			StringBuilder b = new StringBuilder();
			for (Feature f : this)
				b.append(f);
			return b.toString();
		}

		@Override
		public int hashCode() {
			return list.hashCode();
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (!(obj instanceof Query))
				return false;
			return Iterables.elementsEqual(this, (Query)obj);
		}

		private static class Feature {

			private final String key;
			private final Optional value;
			private final Optional literal;

			private Feature(String key, Optional value) {
				this(key, value, false);
			}

			private Feature(String key, Optional value, boolean specifiedAsString) {
				this.key = key;
				this.value = value;
				if (value.isPresent()) {
					String v = value.get();
					if (!specifiedAsString && (v.matches(IDENT_RE) || v.matches(INTEGER_RE)))
						this.literal = Optional.of(v);
					else
						this.literal = Optional.of("\"" + v.replace("\n", "\\A ").replace("\"","\\22 ") + "\"");
				} else
					this.literal = Optional.empty();
			}

			public String getKey() {
				return key;
			}

			public boolean hasValue() {
				return getValue().isPresent();
			}

			public Optional getValue() {
				return value;
			}

			public Optional getLiteral() {
				return literal;
			}

			@Override
			public String toString() {
				StringBuilder b = new StringBuilder();
				String k = getKey();
				if (!k.matches(IDENT_RE))
					throw new RuntimeException();
				b.append("(" + k);
				if (hasValue()) {
					b.append(":");
					b.append(getLiteral().get());
				}
				b.append(")");
				return b.toString();
			}

			@Override
			public int hashCode() {
				final int prime = 31;
				int result = 1;
				result = prime * result + ((key == null) ? 0 : key.hashCode());
				result = prime * result + ((value == null) ? 0 : value.hashCode());
				return result;
			}

			@Override
			public boolean equals(Object obj) {
				if (this == obj)
					return true;
				if (obj == null)
					return false;
				if (getClass() != obj.getClass())
					return false;
				Feature other = (Feature)obj;
				if (key == null) {
					if (other.getKey() != null)
						return false;
				} else if (!key.equals(other.getKey()))
					return false;
				if (value == null) {
					if (other.getValue() != null)
						return false;
				} else if (!value.equals(other.getValue()))
					return false;
				return true;
			}
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy