CSS parser

This commit is contained in:
Vadim Lopatin 2015-12-23 16:32:43 +03:00
parent ac2abfba54
commit 3d338b86b0
1 changed files with 224 additions and 128 deletions

View File

@ -204,9 +204,9 @@ enum CssDeclType : ubyte {
text_align_last, text_align_last,
text_decoration, text_decoration,
hyphenate, // hyphenate hyphenate, // hyphenate
hyphenate2, // -webkit-hyphens _webkit_hyphens, // -webkit-hyphens
hyphenate3, // adobe-hyphenate adobe_hyphenate, // adobe-hyphenate
hyphenate4, // adobe-text-layout adobe_text_layout, // adobe-text-layout
color, color,
background_color, background_color,
vertical_align, vertical_align,
@ -236,9 +236,16 @@ enum CssDeclType : ubyte {
list_style, list_style,
list_style_type, list_style_type,
list_style_position, list_style_position,
list_style_image, list_style_image
stop, }
eol
class CssStyle {
CssDisplay display = CssDisplay.block;
CssWhiteSpace whiteSpace = CssWhiteSpace.inherit;
CssTextAlign textAlign = CssTextAlign.inherit;
CssTextAlign textAlignLast = CssTextAlign.inherit;
CssTextDecoration textDecoration = CssTextDecoration.inherit;
CssHyphenate hyphenate = CssHyphenate.inherit;
} }
/// skip spaces, move to new location, return true if there are some characters left in source line /// skip spaces, move to new location, return true if there are some characters left in source line
@ -281,7 +288,7 @@ private string parseIdent(ref string src) {
private bool skipChar(ref string src, char ch) { private bool skipChar(ref string src, char ch) {
skipSpaces(src); skipSpaces(src);
if (src.length > 0 && src[0] == ':') { if (src.length > 0 && src[0] == ch) {
src = src[1 .. $]; src = src[1 .. $];
skipSpaces(src); skipSpaces(src);
return true; return true;
@ -289,7 +296,7 @@ private bool skipChar(ref string src, char ch) {
return false; return false;
} }
string replaceChar(string s, char from, char to) { private string replaceChar(string s, char from, char to) {
foreach(ch; s) { foreach(ch; s) {
if (ch == from) { if (ch == from) {
char[] buf; char[] buf;
@ -304,74 +311,123 @@ string replaceChar(string s, char from, char to) {
return s; return s;
} }
/// remove trailing _ from string, e.g. "body_" -> "body"
private string removeTrailingUnderscore(string s) {
if (s.endsWith("_"))
return s[0..$-1];
return s;
}
private int parseEnumItem(E)(ref string src, int defValue = -1) if (is(E == enum)) { private int parseEnumItem(E)(ref string src, int defValue = -1) if (is(E == enum)) {
string ident = replaceChar(parseIdent(src), '_', '-'); string ident = replaceChar(parseIdent(src), '-', '_');
foreach(member; EnumMembers!E) { foreach(member; EnumMembers!E) {
if (member.to!string.equal(ident)) if (ident == removeTrailingUnderscore(member.to!string)) {
return member; return member.to!int;
}
} }
return defValue; return defValue;
} }
private CssDeclType parseCssDeclType(ref string src) { private CssDeclType parseCssDeclType(ref string src) {
string ident = parseIdent(src); int n = parseEnumItem!CssDeclType(src, -1);
if (ident.empty) if (n < 0)
return CssDeclType.unknown; return CssDeclType.unknown;
if (!skipChar(src, ':')) // no : after identifier if (!skipChar(src, ':')) // no : after identifier
return CssDeclType.unknown; return CssDeclType.unknown;
switch(ident) with (CssDeclType) { return cast(CssDeclType)n;
case "display": return display; }
case "white-space": return white_space;
case "text-align": return text_align; private bool nextProperty(ref string str) {
case "text-align-last": return text_align_last; int pos = 0;
case "text-decoration": return text_decoration; for (; pos < str.length; pos++) {
case "hyphenate": return hyphenate; // hyphenate char ch = str[pos];
case "-webkit-hyphens": return hyphenate2; // -webkit-hyphens if (ch == '}')
case "-adobe-hyphenate": return hyphenate3; // adobe-hyphenate break;
case "-adobe-text-layout": return hyphenate4; // adobe-text-layout if (ch == ';') {
case "color": return color; pos++;
case "background-color": return background_color; break;
case "vertical-align": return vertical_align; }
case "font-family": return font_family; // id families like serif; sans-serif }
case "font-names": return font_names; // string font name like Arial; Courier str = pos < str.length ? str[pos .. $] : null;
case "font-size": return font_size; skipSpaces(str);
case "font-style": return font_style; return !str.empty && str[0] != '}';
case "font-weight": return font_weight; }
case "text-indent": return text_indent;
case "line-height": return line_height; struct CssDeclItem {
case "letter-spacing": return letter_spacing; CssDeclType type;
case "width": return width; int value;
case "height": return height; string str;
case "margin-left": return margin_left;
case "margin-right": return margin_right; void apply(CssStyle style) {
case "margin-top": return margin_top; switch (type) with (CssDeclType) {
case "margin-bottom": return margin_bottom; case display: style.display = cast(CssDisplay)value; break;
case "margin": return margin; case white_space: style.whiteSpace = cast(CssWhiteSpace)value; break;
case "padding-left": return padding_left; case text_align: style.textAlign = cast(CssTextAlign)value; break;
case "padding-right": return padding_right; case text_align_last: style.textAlignLast = cast(CssTextAlign)value; break;
case "padding-top": return padding_top; case text_decoration: style.textDecoration = cast(CssTextDecoration)value; break;
case "padding-bottom": return padding_bottom;
case "padding": return padding; case _webkit_hyphens: // -webkit-hyphens
case "page-break-before": return page_break_before; case adobe_hyphenate: // adobe-hyphenate
case "page-break-after": return page_break_after; case adobe_text_layout: // adobe-text-layout
case "page-break-inside": return page_break_inside; case hyphenate:
case "list-style": return list_style; style.hyphenate = cast(CssHyphenate)value;
case "list-style-type": return list_style_type; break; // hyphenate
case "list-style-position": return list_style_position;
case "list-style-image": return list_style_image; case color: break;
case background_color: break;
case vertical_align: break;
case font_family: break; // id families like serif, sans-serif
case font_names: break; // string font name like Arial, Courier
case font_size: break;
case font_style: break;
case font_weight: break;
case text_indent: break;
case line_height: break;
case letter_spacing: break;
case width: break;
case height: break;
case margin_left: break;
case margin_right: break;
case margin_top: break;
case margin_bottom: break;
case margin: break;
case padding_left: break;
case padding_right: break;
case padding_top: break;
case padding_bottom: break;
case padding: break;
case page_break_before: break;
case page_break_after: break;
case page_break_inside: break;
case list_style: break;
case list_style_type: break;
case list_style_position: break;
case list_style_image: break;
default: default:
return CssDeclType.unknown; break;
}
} }
} }
/// css declaration like { display: block; margin-top: 10px }
class CssDeclaration { class CssDeclaration {
bool parse(ref string src) { private CssDeclItem[] _list;
void apply(CssStyle style) {
foreach(item; _list)
item.apply(style);
}
bool parse(ref string src, bool mustBeInBrackets = true) {
if (!skipSpaces(src)) if (!skipSpaces(src))
return false; return false;
if (!skipChar(src, '{')) if (mustBeInBrackets && !skipChar(src, '{'))
return false; // decl must start with { return false; // decl must start with {
for (;;) {
CssDeclType propId = parseCssDeclType(src); CssDeclType propId = parseCssDeclType(src);
if (propId != CssDeclType.unknown) {
int n = -1; int n = -1;
string s = null;
switch(propId) with(CssDeclType) { switch(propId) with(CssDeclType) {
case display: n = parseEnumItem!CssDisplay(src, -1); break; case display: n = parseEnumItem!CssDisplay(src, -1); break;
case white_space: n = parseEnumItem!CssWhiteSpace(src, -1); break; case white_space: n = parseEnumItem!CssWhiteSpace(src, -1); break;
@ -379,9 +435,9 @@ class CssDeclaration {
case text_align_last: n = parseEnumItem!CssTextAlign(src, -1); break; case text_align_last: n = parseEnumItem!CssTextAlign(src, -1); break;
case text_decoration: n = parseEnumItem!CssTextDecoration(src, -1); break; case text_decoration: n = parseEnumItem!CssTextDecoration(src, -1); break;
case hyphenate: case hyphenate:
case hyphenate2: case _webkit_hyphens: // -webkit-hyphens
case hyphenate3: case adobe_hyphenate: // adobe-hyphenate
case hyphenate4: case adobe_text_layout: // adobe-text-layout
n = parseEnumItem!CssHyphenate(src, -1); n = parseEnumItem!CssHyphenate(src, -1);
break; // hyphenate break; // hyphenate
case color: case color:
@ -439,6 +495,46 @@ class CssDeclaration {
default: default:
break; break;
} }
return true; if (n >= 0 || !s.empty) {
CssDeclItem item;
item.type = propId;
item.value = n;
item.str = s;
_list ~= item;
}
}
if (!nextProperty(src))
break;
}
if (mustBeInBrackets && !skipChar(src, '}'))
return false;
return _list.length > 0;
}
}
version(unittest) {
void testCSS() {
CssStyle style = new CssStyle();
CssDeclaration decl = new CssDeclaration();
CssWhiteSpace whiteSpace = CssWhiteSpace.inherit;
CssTextAlign textAlign = CssTextAlign.inherit;
CssTextAlign textAlignLast = CssTextAlign.inherit;
CssTextDecoration textDecoration = CssTextDecoration.inherit;
CssHyphenate hyphenate = CssHyphenate.inherit;
string src = "{ display: inline; text-decoration: underline; white-space: pre; text-align: right; text-align-last: left; hyphenate: auto }";
assert(decl.parse(src, true));
assert(style.display == CssDisplay.block);
assert(style.textDecoration == CssTextDecoration.inherit);
assert(style.whiteSpace == CssWhiteSpace.inherit);
assert(style.textAlign == CssTextAlign.inherit);
assert(style.textAlignLast == CssTextAlign.inherit);
assert(style.hyphenate == CssHyphenate.inherit);
decl.apply(style);
assert(style.display == CssDisplay.inline);
assert(style.textDecoration == CssTextDecoration.underline);
assert(style.whiteSpace == CssWhiteSpace.pre);
assert(style.textAlign == CssTextAlign.right);
assert(style.textAlignLast == CssTextAlign.left);
assert(style.hyphenate == CssHyphenate.auto_);
} }
} }