1 package org.daisy.pipeline.css.impl;
2
3 import java.util.AbstractCollection;
4 import java.util.ArrayList;
5 import java.util.HashMap;
6 import java.util.Iterator;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Optional;
10 import java.util.regex.Matcher;
11 import java.util.regex.Pattern;
12
13 import com.google.common.collect.Iterables;
14
15 import org.daisy.pipeline.css.sass.impl.SassMapParser;
16
17 import org.slf4j.Logger;
18 import org.slf4j.LoggerFactory;
19
20 import org.unbescape.css.CssEscape;
21
22 public abstract class StylesheetParametersParser {
23
24 private static final Logger logger = LoggerFactory.getLogger(StylesheetParametersParser.class);
25 private static final Pattern regex = Pattern.compile(
26 (SassMapParser.asRegex().pattern() + "|" + Query.QUERY.pattern()).replaceAll("\\(\\?<[^>]+>", "(?:"));
27
28 private StylesheetParametersParser() {}
29
30 public static Map<String,Object> parse(String expression) throws IllegalArgumentException {
31 Map<String,Object> map = null;
32 try {
33 map = SassMapParser.parse(expression);
34 } catch (IllegalArgumentException e) {
35 Query query = null; {
36 try {
37 query = Query.parse(expression);
38 logger.warn(
39 "Query syntax is deprecated for stylesheet-parameters. Please use Sass map syntax.");
40 } catch (IllegalArgumentException ee) {
41 }
42 }
43 if (query != null) {
44 map = new HashMap<>();
45 for (Query.Feature f : query)
46 map.put(f.getKey(), f.getValue().orElse("true"));
47 }
48 }
49 if (map == null)
50 throw new IllegalArgumentException("Could not parse stylesheet parameter set: " + expression);
51 return map;
52 }
53
54 public static Pattern asRegex() {
55 return regex;
56 }
57
58
59
60
61
62
63
64
65
66 private static class Query extends AbstractCollection<Query.Feature> {
67
68 private static final String IDENT_RE = "[_a-zA-Z][_a-zA-Z0-9-]*";
69 private static final String STRING_RE = "'[^']*'|\"[^\"]*\"";
70 private static final String INTEGER_RE = "0|-?[1-9][0-9]*";
71 private static final Pattern VALUE_RE = Pattern.compile(
72 "(?<ident>" + IDENT_RE + ")|(?<string>" + STRING_RE + ")|(?<integer>" + INTEGER_RE + ")"
73 );
74 private static final Pattern FEATURE_RE = Pattern.compile(
75 "\\(\\s*(?<key>" + IDENT_RE + ")(?:\\s*\\:\\s*(?<value>" + VALUE_RE.pattern() + "))?\\s*\\)"
76 );
77 private static final Pattern FEATURES_RE = Pattern.compile(
78 "\\s*(?:" + FEATURE_RE.pattern() + "\\s*)*"
79 );
80 private static final Pattern QUERY = FEATURES_RE;
81
82
83
84
85 public static Query parse(String query) throws IllegalArgumentException {
86 if (FEATURES_RE.matcher(query).matches()) {
87 Query q = new Query();
88 Matcher m = FEATURE_RE.matcher(query);
89 while (m.find()) {
90 String key = m.group("key");
91 String value = m.group("value");
92 boolean isString = false;
93 if (value != null) {
94 Matcher m2 = VALUE_RE.matcher(value);
95 if (!m2.matches())
96 throw new RuntimeException("Coding error");
97 String ident = m2.group("ident");
98 String string = m2.group("string");
99 String integer = m2.group("integer");
100 if (ident != null)
101 value = ident;
102 else if (string != null && !string.equals("")) {
103 value = CssEscape.unescapeCss(string.substring(1, string.length() - 1));
104 isString = true; }
105 else if (integer != null && !integer.equals(""))
106 value = integer;
107 else
108 throw new RuntimeException("Coding error"); }
109 q.add(new Feature(key, Optional.ofNullable(value), isString)); }
110 return q; }
111 throw new IllegalArgumentException("Could not parse query: " + query);
112 }
113
114 private final List<Feature> list;
115
116 private Query() {
117 this.list = new ArrayList<Feature>();
118 }
119
120 @Override
121 public boolean add(Feature feature) {
122 int index = 0;
123 String key = feature.getKey();
124 for (Feature f : list) {
125 int cmp = f.getKey().compareTo(key);
126 if (cmp > 0) {
127 break;
128 } else if (cmp > 0) {
129 index++;
130 break;
131 }
132 index++;
133 }
134 list.add(index, feature);
135 return true;
136 }
137
138 @Override
139 public int size() {
140 return list.size();
141 }
142
143 @Override
144 public Iterator<Feature> iterator() {
145 return list.iterator();
146 }
147
148 @Override
149 public String toString() {
150 StringBuilder b = new StringBuilder();
151 for (Feature f : this)
152 b.append(f);
153 return b.toString();
154 }
155
156 @Override
157 public int hashCode() {
158 return list.hashCode();
159 }
160
161 @Override
162 public boolean equals(Object obj) {
163 if (this == obj)
164 return true;
165 if (obj == null)
166 return false;
167 if (!(obj instanceof Query))
168 return false;
169 return Iterables.elementsEqual(this, (Query)obj);
170 }
171
172 private static class Feature {
173
174 private final String key;
175 private final Optional<String> value;
176 private final Optional<String> literal;
177
178 private Feature(String key, Optional<String> value) {
179 this(key, value, false);
180 }
181
182 private Feature(String key, Optional<String> value, boolean specifiedAsString) {
183 this.key = key;
184 this.value = value;
185 if (value.isPresent()) {
186 String v = value.get();
187 if (!specifiedAsString && (v.matches(IDENT_RE) || v.matches(INTEGER_RE)))
188 this.literal = Optional.of(v);
189 else
190 this.literal = Optional.of("\"" + v.replace("\n", "\\A ").replace("\"","\\22 ") + "\"");
191 } else
192 this.literal = Optional.empty();
193 }
194
195 public String getKey() {
196 return key;
197 }
198
199 public boolean hasValue() {
200 return getValue().isPresent();
201 }
202
203 public Optional<String> getValue() {
204 return value;
205 }
206
207 public Optional<String> getLiteral() {
208 return literal;
209 }
210
211 @Override
212 public String toString() {
213 StringBuilder b = new StringBuilder();
214 String k = getKey();
215 if (!k.matches(IDENT_RE))
216 throw new RuntimeException();
217 b.append("(" + k);
218 if (hasValue()) {
219 b.append(":");
220 b.append(getLiteral().get());
221 }
222 b.append(")");
223 return b.toString();
224 }
225
226 @Override
227 public int hashCode() {
228 final int prime = 31;
229 int result = 1;
230 result = prime * result + ((key == null) ? 0 : key.hashCode());
231 result = prime * result + ((value == null) ? 0 : value.hashCode());
232 return result;
233 }
234
235 @Override
236 public boolean equals(Object obj) {
237 if (this == obj)
238 return true;
239 if (obj == null)
240 return false;
241 if (getClass() != obj.getClass())
242 return false;
243 Feature other = (Feature)obj;
244 if (key == null) {
245 if (other.getKey() != null)
246 return false;
247 } else if (!key.equals(other.getKey()))
248 return false;
249 if (value == null) {
250 if (other.getValue() != null)
251 return false;
252 } else if (!value.equals(other.getValue()))
253 return false;
254 return true;
255 }
256 }
257 }
258 }