mirror of https://github.com/buggins/dlangui.git
CSS parser
This commit is contained in:
parent
206d9b7c60
commit
0d5c3509f0
|
@ -3,6 +3,7 @@
|
||||||
/**
|
/**
|
||||||
This module contains implementation of CSS support - Cascading Style Sheets.
|
This module contains implementation of CSS support - Cascading Style Sheets.
|
||||||
|
|
||||||
|
Port of CoolReader Engine written in C++.
|
||||||
|
|
||||||
Synopsis:
|
Synopsis:
|
||||||
|
|
||||||
|
@ -19,10 +20,12 @@ module dlangui.core.css;
|
||||||
|
|
||||||
import std.traits;
|
import std.traits;
|
||||||
import std.conv : to;
|
import std.conv : to;
|
||||||
import std.string : startsWith, endsWith;
|
import std.string;
|
||||||
import std.array : empty;
|
import std.array : empty;
|
||||||
import std.algorithm : equal;
|
import std.algorithm : equal;
|
||||||
|
|
||||||
|
import dlangui.core.dom;
|
||||||
|
|
||||||
/// display property values
|
/// display property values
|
||||||
enum CssDisplay : ubyte {
|
enum CssDisplay : ubyte {
|
||||||
inherit,
|
inherit,
|
||||||
|
@ -276,6 +279,181 @@ class CssStyle {
|
||||||
CssValue textIndent = CssValue.inherited;
|
CssValue textIndent = CssValue.inherited;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// selector rule type
|
||||||
|
enum CssSelectorRuleType : ubyte {
|
||||||
|
universal, // *
|
||||||
|
parent, // E > F
|
||||||
|
ancessor, // E F
|
||||||
|
predecessor, // E + F
|
||||||
|
attrset, // E[foo]
|
||||||
|
attreq, // E[foo="value"]
|
||||||
|
attrhas, // E[foo~="value"]
|
||||||
|
attrstarts, // E[foo|="value"]
|
||||||
|
id, // E#id
|
||||||
|
class_ // E.class
|
||||||
|
}
|
||||||
|
|
||||||
|
class CssSelectorRule
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
CssSelectorRuleType _type;
|
||||||
|
elem_id _id;
|
||||||
|
attr_id _attrid;
|
||||||
|
CssSelectorRule _next;
|
||||||
|
string _value;
|
||||||
|
public:
|
||||||
|
this(CssSelectorRuleType type) {
|
||||||
|
_type = type;
|
||||||
|
}
|
||||||
|
this(const CssSelectorRule v) {
|
||||||
|
_type = v._type;
|
||||||
|
_id = v._id;
|
||||||
|
_attrid = v._attrid;
|
||||||
|
_value = v._value;
|
||||||
|
}
|
||||||
|
~this() {
|
||||||
|
//if (_next)
|
||||||
|
// destroy(_next);
|
||||||
|
}
|
||||||
|
|
||||||
|
@property elem_id id() { return _id; }
|
||||||
|
@property void id(elem_id newid) { _id = newid; }
|
||||||
|
@property attr_id attrid() { return _attrid; }
|
||||||
|
@property void setAttr(attr_id newid, string value) { _attrid = newid; _value = value; }
|
||||||
|
@property CssSelectorRule next() { return _next; }
|
||||||
|
@property void next(CssSelectorRule v) { _next = v; }
|
||||||
|
/// check condition for node
|
||||||
|
bool check(ref Node node) {
|
||||||
|
if (!node || !node.parent)
|
||||||
|
return false;
|
||||||
|
switch (_type) with (CssSelectorRuleType) {
|
||||||
|
case parent: // E > F
|
||||||
|
node = node.parent;
|
||||||
|
if (!node)
|
||||||
|
return false;
|
||||||
|
return node.id == _id;
|
||||||
|
|
||||||
|
case ancessor: // E F
|
||||||
|
for (;;) {
|
||||||
|
node = node.parent;
|
||||||
|
if (!node)
|
||||||
|
return false;
|
||||||
|
if (node.id == _id)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case predecessor: // E + F
|
||||||
|
int index = node.index;
|
||||||
|
// while
|
||||||
|
if (index > 0) {
|
||||||
|
Node elem = node.parent.childElement(index-1, _id);
|
||||||
|
if ( elem ) {
|
||||||
|
node = elem;
|
||||||
|
//CRLog::trace("+ selector: found pred element");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//index--;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case attrset: // E[foo]
|
||||||
|
return node.hasAttr(_attrid);
|
||||||
|
|
||||||
|
case attreq: // E[foo="value"]
|
||||||
|
string val = node.attrValue(Ns.any, _attrid);
|
||||||
|
return (val == _value);
|
||||||
|
|
||||||
|
case attrhas: // E[foo~="value"]
|
||||||
|
// one of space separated values
|
||||||
|
string val = node.attrValue(Ns.any, _attrid);
|
||||||
|
int p = val.indexOf(_value);
|
||||||
|
if (p < 0)
|
||||||
|
return false;
|
||||||
|
if ( (p > 0 && val[p - 1] != ' ')
|
||||||
|
|| ( p + _value.length < val.length && val[p + _value.length] != ' '))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case attrstarts: // E[foo|="value"]
|
||||||
|
string val = node.attrValue(Ns.any, _attrid);
|
||||||
|
if (_value.length > val.length)
|
||||||
|
return false;
|
||||||
|
return val[0 .. _value.length] == _value;
|
||||||
|
|
||||||
|
case id: // E#id
|
||||||
|
string val = node.attrValue(Ns.any, Attr.id);
|
||||||
|
return val == _value;
|
||||||
|
|
||||||
|
case class_: // E.class
|
||||||
|
string val = node.attrValue(Ns.any, Attr.class_);
|
||||||
|
return !val.icmp(_value);
|
||||||
|
|
||||||
|
case universal: // *
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** simple CSS selector
|
||||||
|
|
||||||
|
Currently supports only element name and universal selector.
|
||||||
|
|
||||||
|
- * { } - universal selector
|
||||||
|
- element-name { } - selector by element name
|
||||||
|
- element1, element2 { } - several selectors delimited by comma
|
||||||
|
*/
|
||||||
|
class CssSelector {
|
||||||
|
private:
|
||||||
|
uint _id;
|
||||||
|
CssDeclaration _decl;
|
||||||
|
int _specificity;
|
||||||
|
CssSelector _next;
|
||||||
|
CssSelectorRule _rules;
|
||||||
|
void insertRuleStart(CssSelectorRule rule) {
|
||||||
|
}
|
||||||
|
void insertRuleAfterStart(CssSelectorRule rule) {
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
this(CssSelector v) {
|
||||||
|
_id = v._id;
|
||||||
|
_decl = v._decl;
|
||||||
|
_specificity = v._specificity;
|
||||||
|
}
|
||||||
|
this() { }
|
||||||
|
~this() {
|
||||||
|
//if (_next)
|
||||||
|
// destroy(_next);
|
||||||
|
//if (_rules)
|
||||||
|
// destroy(_rules);
|
||||||
|
}
|
||||||
|
bool parse(ref string str) { //, lxmlDocBase * doc
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@property uint tagId() { return _id; }
|
||||||
|
bool check(const Node node) const {
|
||||||
|
// TODO
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/// apply to style if selector matches
|
||||||
|
void apply(const Node node, CssStyle style) const
|
||||||
|
{
|
||||||
|
if (check(node))
|
||||||
|
_decl.apply(style);
|
||||||
|
}
|
||||||
|
void setDeclaration(CssDeclaration decl) {
|
||||||
|
_decl = decl;
|
||||||
|
}
|
||||||
|
int getSpecificity() {
|
||||||
|
return _specificity;
|
||||||
|
}
|
||||||
|
@property CssSelector next() { return _next; }
|
||||||
|
@property void next(CssSelector next) { _next = next; }
|
||||||
|
//lUInt32 getHash();
|
||||||
|
}
|
||||||
|
|
||||||
struct CssDeclItem {
|
struct CssDeclItem {
|
||||||
CssDeclType type = CssDeclType.unknown;
|
CssDeclType type = CssDeclType.unknown;
|
||||||
union {
|
union {
|
||||||
|
@ -284,7 +462,7 @@ struct CssDeclItem {
|
||||||
}
|
}
|
||||||
string str;
|
string str;
|
||||||
|
|
||||||
void apply(CssStyle style) {
|
void apply(CssStyle style) const {
|
||||||
switch (type) with (CssDeclType) {
|
switch (type) with (CssDeclType) {
|
||||||
case display: style.display = cast(CssDisplay)value; break;
|
case display: style.display = cast(CssDisplay)value; break;
|
||||||
case white_space: style.whiteSpace = cast(CssWhiteSpace)value; break;
|
case white_space: style.whiteSpace = cast(CssWhiteSpace)value; break;
|
||||||
|
@ -357,7 +535,7 @@ class CssDeclaration {
|
||||||
_list ~= item;
|
_list ~= item;
|
||||||
}
|
}
|
||||||
|
|
||||||
void apply(CssStyle style) {
|
void apply(CssStyle style) const {
|
||||||
foreach(item; _list)
|
foreach(item; _list)
|
||||||
item.apply(style);
|
item.apply(style);
|
||||||
}
|
}
|
||||||
|
@ -938,9 +1116,9 @@ unittest {
|
||||||
string src = "{ display: inline; text-decoration: underline; white-space: pre; text-align: right; text-align-last: left; "
|
string src = "{ display: inline; text-decoration: underline; white-space: pre; text-align: right; text-align-last: left; "
|
||||||
"hyphenate: auto; width: 70%; height: 1.5pt; margin-left: 2.0em; "
|
"hyphenate: auto; width: 70%; height: 1.5pt; margin-left: 2.0em; "
|
||||||
"font-family: Arial, 'Times New Roman', sans-serif; font-size: 18pt; line-height: 120%; letter-spacing: 2px; font-weight: 300; "
|
"font-family: Arial, 'Times New Roman', sans-serif; font-size: 18pt; line-height: 120%; letter-spacing: 2px; font-weight: 300; "
|
||||||
" }";
|
" }t";
|
||||||
assert(decl.parse(src, true));
|
assert(decl.parse(src, true));
|
||||||
assert(!src.empty && src[0] == '}');
|
assert(!src.empty && src[0] == 't');
|
||||||
assert(style.display == CssDisplay.block);
|
assert(style.display == CssDisplay.block);
|
||||||
assert(style.textDecoration == CssTextDecoration.inherit);
|
assert(style.textDecoration == CssTextDecoration.inherit);
|
||||||
assert(style.whiteSpace == CssWhiteSpace.inherit);
|
assert(style.whiteSpace == CssWhiteSpace.inherit);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
/**
|
/**
|
||||||
This module contains implementation DOM - document object model.
|
This module contains implementation DOM - document object model.
|
||||||
|
|
||||||
|
Port of CoolReader Engine written in C++.
|
||||||
|
|
||||||
Synopsis:
|
Synopsis:
|
||||||
|
|
||||||
|
@ -84,6 +85,16 @@ public:
|
||||||
string attrValue(ns_id nsid, attr_id attrid) { return null; }
|
string attrValue(ns_id nsid, attr_id attrid) { return null; }
|
||||||
/// get attribute value by namespace and attribute ids
|
/// get attribute value by namespace and attribute ids
|
||||||
string attrValue(string nsname, string attrname) { return attrValue(_document.nsId(nsname), _document.attrId(attrname)); }
|
string attrValue(string nsname, string attrname) { return attrValue(_document.nsId(nsname), _document.attrId(attrname)); }
|
||||||
|
/// returns true if node has attribute with specified name
|
||||||
|
bool hasAttr(string attrname) {
|
||||||
|
return hasAttr(document.attrId(attrname));
|
||||||
|
}
|
||||||
|
/// returns true if node has attribute with specified id
|
||||||
|
bool hasAttr(attr_id attrid) {
|
||||||
|
if (Attribute a = attr(Ns.any, attrid))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// child nodes
|
// child nodes
|
||||||
|
|
||||||
|
@ -101,6 +112,14 @@ public:
|
||||||
/// return node index in parent's child node collection, -1 if not found
|
/// return node index in parent's child node collection, -1 if not found
|
||||||
@property int index() { return _parent ? _parent.childIndex(this) : -1; }
|
@property int index() { return _parent ? _parent.childIndex(this) : -1; }
|
||||||
|
|
||||||
|
/// returns child node by index and optionally compares its tag id, returns null if child with this index is not an element or id does not match
|
||||||
|
Element childElement(int index, elem_id id = 0) {
|
||||||
|
Element res = cast(Element)child(index);
|
||||||
|
if (res && (id == 0 || res.id == id))
|
||||||
|
return res;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// append text child
|
/// append text child
|
||||||
Node appendText(dstring s, int index = -1) { assert(false); }
|
Node appendText(dstring s, int index = -1) { assert(false); }
|
||||||
/// append element child - by namespace and tag names
|
/// append element child - by namespace and tag names
|
||||||
|
|
Loading…
Reference in New Issue