1 package org.daisy.pipeline.css.sass.impl;
2
3 import java.util.regex.Pattern;
4
5 import com.google.common.base.CaseFormat;
6
7 import org.daisy.pipeline.css.CssAnalyzer;
8 import org.daisy.pipeline.datatypes.DatatypeRegistry;
9 import org.daisy.pipeline.datatypes.DatatypeService;
10 import org.daisy.pipeline.datatypes.XMLBasedDatatypeService;
11
12 import org.slf4j.Logger;
13 import org.slf4j.LoggerFactory;
14
15 import org.unbescape.css.CssEscape;
16
17 import org.w3c.dom.Element;
18 import org.w3c.dom.Node;
19
20 class SassVariable implements CssAnalyzer.SassVariable {
21
22 private final static Logger logger = LoggerFactory.getLogger(SassVariable.class.getName());
23
24 private final String name;
25 private final String nicename;
26 private final String description;
27 private final String value;
28 private boolean isDefault = true;
29 private final DatatypeService type;
30
31
32
33
34 SassVariable(String name, String value) {
35 this(name, null, value, null);
36 isDefault = false;
37 }
38
39
40
41
42 SassVariable(String name, Comment precedingComment, String defaultValue, DatatypeRegistry datatypes) {
43 this.name = name;
44 DoxygenComment doxygenComment; {
45 doxygenComment = null;
46 if (precedingComment != null && precedingComment.text.startsWith("*"))
47 try {
48 doxygenComment = DoxygenComment.of(precedingComment);
49 } catch (IllegalArgumentException e) {
50 logger.warn("Expected a Doxygen comment but got: " + precedingComment, e);
51 }
52 }
53 String nicename = null;
54 String description = "";
55 DatatypeService type = null; {
56 if (doxygenComment != null) {
57 if (doxygenComment.varName.isPresent() && !doxygenComment.varName.get().equals(name))
58 logger.warn("Declaration of variable $" + name + " was preceded by a Doxygen comment " +
59 "describing a different variable: " + precedingComment);
60 else {
61 nicename = doxygenComment.brief.orElse(null);
62 description = doxygenComment.body;
63 if (doxygenComment.type.isPresent()) {
64 String t = doxygenComment.type.get();
65 if ("xs:string".equals(t) || "string".equals(t))
66 type = DatatypeService.XS_STRING;
67 else if ("xs:integer".equals(t) || "integer".equals(t))
68 type = DatatypeService.XS_INTEGER;
69 else if ("xs:nonNegativeInteger".equals(t) || "nonNegativeInteger".equals(t))
70 type = DatatypeService.XS_NON_NEGATIVE_INTEGER;
71 else if ("xs:boolean".equals(t) || "boolean".equals(t))
72 type = DatatypeService.XS_BOOLEAN;
73 else if ("xs:anyURI".equals(t) || "anyURI".equals(t))
74 type = DatatypeService.XS_ANY_URI;
75 else if ("anyFileURI".equals(t))
76 type = DatatypeService.ANY_FILE_URI;
77 else if ("anyDirURI".equals(t))
78 type = DatatypeService.ANY_DIR_URI;
79 else {
80 type = datatypes != null ? datatypes.getDatatype(t).orNull() : null;
81 if (type == null)
82 try {
83 throw new IllegalArgumentException(
84 "Invalid type in variable declaration '" + type + "': does not match a known data type");
85 } catch (IllegalArgumentException e) {
86 logger.warn("Invalid type in Doxygen comment: " + precedingComment, e);
87 }
88 }
89 } else if (doxygenComment.typeDef.isPresent()) {
90 Element def = doxygenComment.typeDef.get();
91 type = new XMLBasedDatatypeService() {
92 @Override
93 protected Node readDocument() throws Exception {
94 return def;
95 }
96 };
97 try {
98
99 type.validate("x");
100 } catch (RuntimeException e) {
101 type = null;
102 logger.warn("Invalid type declaration in Doxygen comment: " + precedingComment, e);
103 }
104 if (type != null && datatypes != null)
105 datatypes.registerVolatile(type);
106 }
107 }
108 } else if (precedingComment != null)
109 description = Pattern.compile("^\\s*\\*", Pattern.MULTILINE)
110 .matcher(precedingComment.text)
111 .replaceAll("")
112 .replaceAll("\\s+", " ")
113 .trim();
114 }
115 if (nicename == null)
116 nicename = name.substring(0, 1).toUpperCase()
117 + CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name)
118 .replace('_', ' ')
119 .replace('-', ' ')
120 .toLowerCase()
121 .substring(1);
122 if (type == null) {
123
124 if ("true".equals(defaultValue) || "false".equals(defaultValue))
125 type = DatatypeService.XS_BOOLEAN;
126 else {
127 try {
128 Integer.parseInt(defaultValue);
129 type = DatatypeService.XS_INTEGER;
130 } catch (NumberFormatException e) {
131 type = DatatypeService.XS_STRING;
132 }
133 }
134 }
135 if (type == DatatypeService.XS_STRING) {
136 if (defaultValue.startsWith("\"") || defaultValue.startsWith("'")) {
137 if ((defaultValue.startsWith("\"") && !defaultValue.endsWith("\""))
138 || (defaultValue.startsWith("'") && !defaultValue.endsWith("'")))
139 throw new IllegalArgumentException("Invalid value in variable declaration: " + defaultValue);
140 defaultValue = CssEscape.unescapeCss(defaultValue.substring(1, defaultValue.length() - 1));
141 }
142 }
143 this.nicename = nicename;
144 this.description = description;
145 this.value = defaultValue;
146 this.type = type;
147 }
148
149 @Override
150 public String getName() {
151 return name;
152 }
153
154 @Override
155 public String getNiceName() {
156 return nicename;
157 }
158
159 @Override
160 public String getDescription() {
161 return description;
162 }
163
164 @Override
165 public String getValue() {
166 return value;
167 }
168
169 @Override
170 public boolean isDefault() {
171 return isDefault;
172 }
173
174 @Override
175 public DatatypeService getType() {
176 return type;
177 }
178
179 @Override
180 public String toString() {
181 return "$" + name + ": " + value + (isDefault ? " !default" : "") + ";";
182 }
183 }