Я не удержался, чтобы реализовать эту задачу написания парсера CSS совершенно другим способом. Я разбил задачу разбора на множество маленьких.
- Наименьшее называется
skipWhitespace
, так как оно понадобится везде при разборе текстовых файлов.
- Следующим является
parseProperty
, который читает одно свойство вида name:value;
.
- Исходя из этого,
parseSelector
читает полный селектор CSS, начиная с имени селектора, открывающей скобки, возможно многих свойств и заканчивая закрывающей скобкой.
- Все еще основываясь на этом,
parseFile
читает полный файл, состоящий, возможно, из множества селекторов.
Обратите внимание, насколько тщательно я проверил, достаточно ли index
. Я делал это перед каждым доступом к массиву chars
.
Я использовал LinkedHashMap
s для сохранения свойств и селекторов, потому что карты такого типа запоминают, в каком порядке были вставлены объекты. Обычные HashMap
s не делают этого.
Задача синтаксического анализа текстового файла, как правило, довольно сложна, и эта программа только пытается обработать основы CSS. Если вам нужен полный CSS-парсер, вам обязательно стоит поискать готовый. Этот не может обрабатывать @media
или подобные вещи, где у вас есть вложенные блоки. Но не должно быть слишком сложно добавить его в существующий код.
Этот парсер не будет хорошо обрабатывать комментарии CSS. Это только ожидает их в нескольких местах. Если комментарии появляются в других местах, парсер не будет рассматривать их как комментарии.
import java.util.LinkedHashMap;
import java.util.Map;
public class CssParser {
private final char[] chars;
private int index;
public Debugger(String code) {
this.chars = code.toCharArray();
this.index = 0;
}
private void skipWhitespace() {
/*
* Here you should also skip comments in the CSS file, which either look
* like this comment or start with a // and go until the end of line.
*/
while (index < chars.length && Character.isWhitespace(chars[index]))
index++;
}
private void parseProperty(String selector, Map<String, String> properties) {
skipWhitespace();
// get the CSS property name
StringBuilder sb = new StringBuilder();
while (index < chars.length && chars[index] != ':')
sb.append(chars[index++]);
String propertyName = sb.toString().trim();
if (index == chars.length)
throw new IllegalArgumentException("Expected a colon at index " + index + ".");
// skip the colon
index++;
// get the CSS property value
sb.setLength(0);
while (index < chars.length && chars[index] != ';' && chars[index] != '}')
sb.append(chars[index++]);
String propertyValue = sb.toString().trim();
/*
* Here is the check for duplicate property definitions. The method
* Map.put(Object, Object) always returns the value that had been stored
* under the given name before.
*/
String previousValue = properties.put(propertyName, propertyValue);
if (previousValue != null)
throw new IllegalArgumentException("Duplicate property \"" + propertyName + "\" in selector \"" + selector + "\".");
if (index < chars.length && chars[index] == ';')
index++;
skipWhitespace();
}
private void parseSelector(Map<String, Map<String, String>> selectors) {
skipWhitespace();
// get the CSS selector
StringBuilder sb = new StringBuilder();
while (index < chars.length && chars[index] != '{')
sb.append(chars[index++]);
String selector = sb.toString().trim();
if (index == chars.length)
throw new IllegalArgumentException("CSS Selector name \"" + selector + "\" without content.");
// skip the opening brace
index++;
skipWhitespace();
Map<String, String> properties = new LinkedHashMap<String, String>();
selectors.put(selector, properties);
while (index < chars.length && chars[index] != '}') {
parseProperty(selector, properties);
skipWhitespace();
}
// skip the closing brace
index++;
}
private Map<String, Map<String, String>> parseFile() {
Map<String, Map<String, String>> selectors = new LinkedHashMap<String, Map<String, String>>();
while (index < chars.length) {
parseSelector(selectors);
skipWhitespace();
}
return selectors;
}
public static void main(String[] args) {
CssParser parser = new CssParser("body {margin:prueba;A:B;a:Arial, Courier New, \"monospace\";\n}");
Map<String, Map<String, String>> selectors = parser.parseFile();
System.out.println("There are " + selectors.size() + " selectors.");
for (Map.Entry<String, Map<String, String>> entry : selectors.entrySet()) {
String selector = entry.getKey();
Map<String, String> properties = entry.getValue();
System.out.println("Selector " + selector + ":");
for (Map.Entry<String, String> property : properties.entrySet()) {
String name = property.getKey();
String value = property.getValue();
System.out.println(" Property name \"" + name + "\" value \"" + value + "\"");
}
}
}
}