1   package org.daisy.pipeline.css.sass.impl;
2   
3   import java.util.HashMap;
4   import java.util.Map;
5   
6   import java.util.regex.Matcher;
7   import java.util.regex.Pattern;
8   
9   import org.unbescape.css.CssEscape;
10  
11  /**
12   * <a href="https://sass-lang.com/documentation/values/maps/">Sass map</a> parser.
13   */
14  public abstract class SassMapParser {
15  
16  	private static final String IDENT_RE = "[_a-zA-Z][_a-zA-Z0-9-]*";
17  	private static final String STRING_RE = "'[^']*'|\"[^\"]*\"";
18  	private static final String INTEGER_RE = "0|-?[1-9][0-9]*";
19  	private static final Pattern VALUE_RE = Pattern.compile(
20  		"(?<ident>" + IDENT_RE + ")|(?<string>" + STRING_RE + ")|(?<integer>" + INTEGER_RE + ")"
21  	);
22  	private static final Pattern KEY_VALUE_RE = Pattern.compile(
23  		"(?<key>" + IDENT_RE + ")\\s*:\\s*(?<value>" + VALUE_RE.pattern() + ")"
24  	);
25  	private static final Pattern MAP_RE = Pattern.compile(
26  		("\\s*\\(\\s*(?:" + KEY_VALUE_RE.pattern() + "\\s*(?:,\\s*" + KEY_VALUE_RE.pattern()  + "\\s*)*)?\\)\\s*")
27  		.replaceAll("\\(\\?<[^>]+>", "(?:")
28  	);
29  
30  	private SassMapParser() {}
31  
32  	public static Map<String,Object> parse(String expression) throws IllegalArgumentException {
33  		if (!MAP_RE.matcher(expression).matches())
34  			throw new IllegalArgumentException("Could not parse Sass map: " + expression);
35  		Map<String,Object> map = new HashMap<>();
36  		Matcher m = KEY_VALUE_RE.matcher(expression);
37  		while (m.find()) {
38  			String key = m.group("key");
39  			String value = m.group("value");
40  			boolean isString = false;
41  			Matcher m2 = VALUE_RE.matcher(value);
42  			if (!m2.matches())
43  				throw new RuntimeException("Coding error");
44  			String ident = m2.group("ident");
45  			String string = m2.group("string");
46  			String integer = m2.group("integer");
47  			if (ident != null) {
48  				if ("true".equals(ident))
49  					map.put(key, Boolean.TRUE);
50  				else if ("false".equals(ident))
51  					map.put(key, Boolean.FALSE);
52  				else
53  					map.put(key, ident);;
54  			} else if (string != null && !string.equals(""))
55  				map.put(key, CssEscape.unescapeCss(string.substring(1, string.length() - 1)));
56  			else if (integer != null && !integer.equals(""))
57  				map.put(key, Integer.parseInt(integer));
58  			else
59  				throw new RuntimeException("Coding error");
60  		}
61  		return map;
62  	}
63  
64  	public static Pattern asRegex() {
65  		return MAP_RE;
66  	}
67  }