Death to C++
This commit is contained in:
956
vendor/lunasvg/source/svgparser.cpp
vendored
956
vendor/lunasvg/source/svgparser.cpp
vendored
@@ -1,956 +0,0 @@
|
||||
#include "lunasvg.h"
|
||||
#include "svgelement.h"
|
||||
#include "svgparserutils.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
struct SimpleSelector;
|
||||
|
||||
using Selector = std::vector<SimpleSelector>;
|
||||
using SelectorList = std::vector<Selector>;
|
||||
|
||||
struct AttributeSelector {
|
||||
enum class MatchType {
|
||||
None,
|
||||
Equals,
|
||||
Contains,
|
||||
Includes,
|
||||
StartsWith,
|
||||
EndsWith,
|
||||
DashEquals
|
||||
};
|
||||
|
||||
MatchType matchType{MatchType::None};
|
||||
PropertyID id{PropertyID::Unknown};
|
||||
std::string value;
|
||||
};
|
||||
|
||||
struct PseudoClassSelector {
|
||||
enum class Type {
|
||||
Unknown,
|
||||
Empty,
|
||||
Root,
|
||||
Is,
|
||||
Not,
|
||||
FirstChild,
|
||||
LastChild,
|
||||
OnlyChild,
|
||||
FirstOfType,
|
||||
LastOfType,
|
||||
OnlyOfType
|
||||
};
|
||||
|
||||
Type type{Type::Unknown};
|
||||
SelectorList subSelectors;
|
||||
};
|
||||
|
||||
struct SimpleSelector {
|
||||
enum class Combinator {
|
||||
None,
|
||||
Descendant,
|
||||
Child,
|
||||
DirectAdjacent,
|
||||
InDirectAdjacent
|
||||
};
|
||||
|
||||
explicit SimpleSelector(Combinator combinator) : combinator(combinator) {}
|
||||
|
||||
Combinator combinator{Combinator::Descendant};
|
||||
ElementID id{ElementID::Star};
|
||||
std::vector<AttributeSelector> attributeSelectors;
|
||||
std::vector<PseudoClassSelector> pseudoClassSelectors;
|
||||
};
|
||||
|
||||
struct Declaration {
|
||||
int specificity;
|
||||
PropertyID id;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
using DeclarationList = std::vector<Declaration>;
|
||||
|
||||
struct Rule {
|
||||
SelectorList selectors;
|
||||
DeclarationList declarations;
|
||||
};
|
||||
|
||||
class RuleData {
|
||||
public:
|
||||
RuleData(const Selector& selector, const DeclarationList& declarations, size_t specificity, size_t position)
|
||||
: m_selector(selector), m_declarations(declarations), m_specificity(specificity), m_position(position)
|
||||
{}
|
||||
|
||||
bool isLessThan(const RuleData& rule) const { return std::tie(m_specificity, m_position) < std::tie(rule.m_specificity, rule.m_position); }
|
||||
|
||||
const Selector& selector() const { return m_selector; }
|
||||
const DeclarationList& declarations() const { return m_declarations; }
|
||||
size_t specificity() const { return m_specificity; }
|
||||
size_t position() const { return m_position; }
|
||||
|
||||
bool match(const SVGElement* element) const;
|
||||
|
||||
private:
|
||||
Selector m_selector;
|
||||
DeclarationList m_declarations;
|
||||
size_t m_specificity;
|
||||
size_t m_position;
|
||||
};
|
||||
|
||||
inline bool operator<(const RuleData& a, const RuleData& b) { return a.isLessThan(b); }
|
||||
|
||||
using RuleDataList = std::vector<RuleData>;
|
||||
|
||||
constexpr bool equals(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
return value.compare(subvalue) == 0;
|
||||
}
|
||||
|
||||
constexpr bool contains(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
return value.find(subvalue) != std::string_view::npos;
|
||||
}
|
||||
|
||||
constexpr bool includes(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
if(subvalue.empty() || subvalue.length() > value.length())
|
||||
return false;
|
||||
std::string_view input(value);
|
||||
while(!input.empty()) {
|
||||
skipOptionalSpaces(input);
|
||||
std::string_view start(input);
|
||||
while(!input.empty() && !IS_WS(input.front()))
|
||||
input.remove_prefix(1);
|
||||
if(subvalue == start.substr(0, start.length() - input.length())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr bool startswith(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
if(subvalue.empty() || subvalue.length() > value.length())
|
||||
return false;
|
||||
return subvalue == value.substr(0, subvalue.size());
|
||||
}
|
||||
|
||||
constexpr bool endswith(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
if(subvalue.empty() || subvalue.length() > value.length())
|
||||
return false;
|
||||
return subvalue == value.substr(value.size() - subvalue.size(), subvalue.size());
|
||||
}
|
||||
|
||||
constexpr bool dashequals(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
if(startswith(value, subvalue))
|
||||
return (value.length() == subvalue.length() || value.at(subvalue.length()) == '-');
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool matchAttributeSelector(const AttributeSelector& selector, const SVGElement* element)
|
||||
{
|
||||
const auto& value = element->getAttribute(selector.id);
|
||||
if(selector.matchType == AttributeSelector::MatchType::None)
|
||||
return !value.empty();
|
||||
if(selector.matchType == AttributeSelector::MatchType::Equals)
|
||||
return equals(value, selector.value);
|
||||
if(selector.matchType == AttributeSelector::MatchType::Contains)
|
||||
return contains(value, selector.value);
|
||||
if(selector.matchType == AttributeSelector::MatchType::Includes)
|
||||
return includes(value, selector.value);
|
||||
if(selector.matchType == AttributeSelector::MatchType::StartsWith)
|
||||
return startswith(value, selector.value);
|
||||
if(selector.matchType == AttributeSelector::MatchType::EndsWith)
|
||||
return endswith(value, selector.value);
|
||||
if(selector.matchType == AttributeSelector::MatchType::DashEquals)
|
||||
return dashequals(value, selector.value);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool matchSimpleSelector(const SimpleSelector& selector, const SVGElement* element);
|
||||
|
||||
static bool matchPseudoClassSelector(const PseudoClassSelector& selector, const SVGElement* element)
|
||||
{
|
||||
if(selector.type == PseudoClassSelector::Type::Empty)
|
||||
return element->children().empty();
|
||||
if(selector.type == PseudoClassSelector::Type::Root)
|
||||
return element->isRootElement();
|
||||
if(selector.type == PseudoClassSelector::Type::Is) {
|
||||
for(const auto& subSelector : selector.subSelectors) {
|
||||
for(const auto& simpleSelector : subSelector) {
|
||||
if(!matchSimpleSelector(simpleSelector, element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(selector.type == PseudoClassSelector::Type::Not) {
|
||||
for(const auto& subSelector : selector.subSelectors) {
|
||||
for(const auto& simpleSelector : subSelector) {
|
||||
if(matchSimpleSelector(simpleSelector, element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(selector.type == PseudoClassSelector::Type::FirstChild)
|
||||
return !element->previousElement();
|
||||
if(selector.type == PseudoClassSelector::Type::LastChild)
|
||||
return !element->nextElement();
|
||||
if(selector.type == PseudoClassSelector::Type::OnlyChild)
|
||||
return !(element->previousElement() || element->nextElement());
|
||||
if(selector.type == PseudoClassSelector::Type::FirstOfType) {
|
||||
auto sibling = element->previousElement();
|
||||
while(sibling) {
|
||||
if(sibling->id() == element->id())
|
||||
return false;
|
||||
sibling = sibling->previousElement();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(selector.type == PseudoClassSelector::Type::LastOfType) {
|
||||
auto sibling = element->nextElement();
|
||||
while(sibling) {
|
||||
if(sibling->id() == element->id())
|
||||
return false;
|
||||
sibling = sibling->nextElement();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool matchSimpleSelector(const SimpleSelector& selector, const SVGElement* element)
|
||||
{
|
||||
if(selector.id != ElementID::Star && selector.id != element->id())
|
||||
return false;
|
||||
for(const auto& sel : selector.attributeSelectors) {
|
||||
if(!matchAttributeSelector(sel, element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto& sel : selector.pseudoClassSelectors) {
|
||||
if(!matchPseudoClassSelector(sel, element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool matchSelector(const Selector& selector, const SVGElement* element)
|
||||
{
|
||||
if(selector.empty())
|
||||
return false;
|
||||
auto it = selector.rbegin();
|
||||
auto end = selector.rend();
|
||||
if(!matchSimpleSelector(*it, element)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto combinator = it->combinator;
|
||||
++it;
|
||||
|
||||
while(it != end) {
|
||||
switch(combinator) {
|
||||
case SimpleSelector::Combinator::Child:
|
||||
case SimpleSelector::Combinator::Descendant:
|
||||
element = element->parentElement();
|
||||
break;
|
||||
case SimpleSelector::Combinator::DirectAdjacent:
|
||||
case SimpleSelector::Combinator::InDirectAdjacent:
|
||||
element = element->previousElement();
|
||||
break;
|
||||
case SimpleSelector::Combinator::None:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if(element == nullptr)
|
||||
return false;
|
||||
if(matchSimpleSelector(*it, element)) {
|
||||
combinator = it->combinator;
|
||||
++it;
|
||||
} else if(combinator != SimpleSelector::Combinator::Descendant
|
||||
&& combinator != SimpleSelector::Combinator::InDirectAdjacent) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuleData::match(const SVGElement* element) const
|
||||
{
|
||||
return matchSelector(m_selector, element);
|
||||
}
|
||||
|
||||
constexpr bool IS_CSS_STARTNAMECHAR(int c) { return IS_ALPHA(c) || c == '_' || c == '-'; }
|
||||
constexpr bool IS_CSS_NAMECHAR(int c) { return IS_CSS_STARTNAMECHAR(c) || IS_NUM(c); }
|
||||
|
||||
inline bool readCSSIdentifier(std::string_view& input, std::string& output)
|
||||
{
|
||||
if(input.empty() || !IS_CSS_STARTNAMECHAR(input.front()))
|
||||
return false;
|
||||
output.clear();
|
||||
do {
|
||||
output.push_back(input.front());
|
||||
input.remove_prefix(1);
|
||||
} while(!input.empty() && IS_CSS_NAMECHAR(input.front()));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseTagSelector(std::string_view& input, SimpleSelector& simpleSelector)
|
||||
{
|
||||
std::string name;
|
||||
if(skipDelimiter(input, '*'))
|
||||
simpleSelector.id = ElementID::Star;
|
||||
else if(readCSSIdentifier(input, name))
|
||||
simpleSelector.id = elementid(name);
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseIdSelector(std::string_view& input, SimpleSelector& simpleSelector)
|
||||
{
|
||||
AttributeSelector a;
|
||||
a.id = PropertyID::Id;
|
||||
a.matchType = AttributeSelector::MatchType::Equals;
|
||||
if(!readCSSIdentifier(input, a.value))
|
||||
return false;
|
||||
simpleSelector.attributeSelectors.push_back(std::move(a));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseClassSelector(std::string_view& input, SimpleSelector& simpleSelector)
|
||||
{
|
||||
AttributeSelector a;
|
||||
a.id = PropertyID::Class;
|
||||
a.matchType = AttributeSelector::MatchType::Includes;
|
||||
if(!readCSSIdentifier(input, a.value))
|
||||
return false;
|
||||
simpleSelector.attributeSelectors.push_back(std::move(a));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseAttributeSelector(std::string_view& input, SimpleSelector& simpleSelector)
|
||||
{
|
||||
std::string name;
|
||||
skipOptionalSpaces(input);
|
||||
if(!readCSSIdentifier(input, name))
|
||||
return false;
|
||||
AttributeSelector a;
|
||||
a.id = propertyid(name);
|
||||
a.matchType = AttributeSelector::MatchType::None;
|
||||
if(skipDelimiter(input, '='))
|
||||
a.matchType = AttributeSelector::MatchType::Equals;
|
||||
else if(skipString(input, "*="))
|
||||
a.matchType = AttributeSelector::MatchType::Contains;
|
||||
else if(skipString(input, "~="))
|
||||
a.matchType = AttributeSelector::MatchType::Includes;
|
||||
else if(skipString(input, "^="))
|
||||
a.matchType = AttributeSelector::MatchType::StartsWith;
|
||||
else if(skipString(input, "$="))
|
||||
a.matchType = AttributeSelector::MatchType::EndsWith;
|
||||
else if(skipString(input, "|="))
|
||||
a.matchType = AttributeSelector::MatchType::DashEquals;
|
||||
if(a.matchType != AttributeSelector::MatchType::None) {
|
||||
skipOptionalSpaces(input);
|
||||
if(!readCSSIdentifier(input, a.value)) {
|
||||
if(input.empty() || !(input.front() == '\"' || input.front() == '\''))
|
||||
return false;
|
||||
auto quote = input.front();
|
||||
input.remove_prefix(1);
|
||||
auto n = input.find(quote);
|
||||
if(n == std::string_view::npos)
|
||||
return false;
|
||||
a.value.assign(input.substr(0, n));
|
||||
input.remove_prefix(n + 1);
|
||||
}
|
||||
}
|
||||
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, ']'))
|
||||
return false;
|
||||
simpleSelector.attributeSelectors.push_back(std::move(a));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseSelectors(std::string_view& input, SelectorList& selectors);
|
||||
|
||||
static bool parsePseudoClassSelector(std::string_view& input, SimpleSelector& simpleSelector)
|
||||
{
|
||||
std::string name;
|
||||
if(!readCSSIdentifier(input, name))
|
||||
return false;
|
||||
PseudoClassSelector selector;
|
||||
if(name.compare("empty") == 0)
|
||||
selector.type = PseudoClassSelector::Type::Empty;
|
||||
else if(name.compare("root") == 0)
|
||||
selector.type = PseudoClassSelector::Type::Root;
|
||||
else if(name.compare("not") == 0)
|
||||
selector.type = PseudoClassSelector::Type::Not;
|
||||
else if(name.compare("first-child") == 0)
|
||||
selector.type = PseudoClassSelector::Type::FirstChild;
|
||||
else if(name.compare("last-child") == 0)
|
||||
selector.type = PseudoClassSelector::Type::LastChild;
|
||||
else if(name.compare("only-child") == 0)
|
||||
selector.type = PseudoClassSelector::Type::OnlyChild;
|
||||
else if(name.compare("first-of-type") == 0)
|
||||
selector.type = PseudoClassSelector::Type::FirstOfType;
|
||||
else if(name.compare("last-of-type") == 0)
|
||||
selector.type = PseudoClassSelector::Type::LastOfType;
|
||||
else if(name.compare("only-of-type") == 0)
|
||||
selector.type = PseudoClassSelector::Type::OnlyOfType;
|
||||
if(selector.type == PseudoClassSelector::Type::Is || selector.type == PseudoClassSelector::Type::Not) {
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, '('))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
if(!parseSelectors(input, selector.subSelectors))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, ')')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
simpleSelector.pseudoClassSelectors.push_back(std::move(selector));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseSimpleSelector(std::string_view& input, SimpleSelector& simpleSelector, bool& failed)
|
||||
{
|
||||
auto consumed = parseTagSelector(input, simpleSelector);
|
||||
do {
|
||||
if(skipDelimiter(input, '#'))
|
||||
failed = !parseIdSelector(input, simpleSelector);
|
||||
else if(skipDelimiter(input, '.'))
|
||||
failed = !parseClassSelector(input, simpleSelector);
|
||||
else if(skipDelimiter(input, '['))
|
||||
failed = !parseAttributeSelector(input, simpleSelector);
|
||||
else if(skipDelimiter(input, ':'))
|
||||
failed = !parsePseudoClassSelector(input, simpleSelector);
|
||||
else
|
||||
break;
|
||||
consumed = true;
|
||||
} while(!failed);
|
||||
return consumed && !failed;
|
||||
}
|
||||
|
||||
static bool parseCombinator(std::string_view& input, SimpleSelector::Combinator& combinator)
|
||||
{
|
||||
combinator = SimpleSelector::Combinator::None;
|
||||
while(!input.empty() && IS_WS(input.front())) {
|
||||
combinator = SimpleSelector::Combinator::Descendant;
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
|
||||
if(skipDelimiterAndOptionalSpaces(input, '>'))
|
||||
combinator = SimpleSelector::Combinator::Child;
|
||||
else if(skipDelimiterAndOptionalSpaces(input, '+'))
|
||||
combinator = SimpleSelector::Combinator::DirectAdjacent;
|
||||
else if(skipDelimiterAndOptionalSpaces(input, '~'))
|
||||
combinator = SimpleSelector::Combinator::InDirectAdjacent;
|
||||
return combinator != SimpleSelector::Combinator::None;
|
||||
}
|
||||
|
||||
static bool parseSelector(std::string_view& input, Selector& selector)
|
||||
{
|
||||
auto combinator = SimpleSelector::Combinator::None;
|
||||
do {
|
||||
bool failed = false;
|
||||
SimpleSelector simpleSelector(combinator);
|
||||
if(!parseSimpleSelector(input, simpleSelector, failed))
|
||||
return !failed && (combinator == SimpleSelector::Combinator::Descendant);
|
||||
selector.push_back(std::move(simpleSelector));
|
||||
} while(parseCombinator(input, combinator));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseSelectors(std::string_view& input, SelectorList& selectors)
|
||||
{
|
||||
do {
|
||||
Selector selector;
|
||||
if(!parseSelector(input, selector))
|
||||
return false;
|
||||
selectors.push_back(std::move(selector));
|
||||
} while(skipDelimiterAndOptionalSpaces(input, ','));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseDeclarations(std::string_view& input, DeclarationList& declarations)
|
||||
{
|
||||
if(!skipDelimiter(input, '{'))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
do {
|
||||
std::string name;
|
||||
if(!readCSSIdentifier(input, name))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, ':'))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
std::string_view value(input);
|
||||
while(!input.empty() && !(input.front() == '!' || input.front() == ';' || input.front() == '}'))
|
||||
input.remove_prefix(1);
|
||||
value.remove_suffix(input.length());
|
||||
stripTrailingSpaces(value);
|
||||
|
||||
Declaration declaration;
|
||||
declaration.specificity = 0x10;
|
||||
declaration.id = csspropertyid(name);
|
||||
declaration.value.assign(value);
|
||||
if(skipDelimiter(input, '!')) {
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipString(input, "important"))
|
||||
return false;
|
||||
declaration.specificity = 0x1000;
|
||||
}
|
||||
|
||||
if(declaration.id != PropertyID::Unknown)
|
||||
declarations.push_back(std::move(declaration));
|
||||
skipOptionalSpacesOrDelimiter(input, ';');
|
||||
} while(!input.empty() && input.front() != '}');
|
||||
return skipDelimiter(input, '}');
|
||||
}
|
||||
|
||||
static bool parseRule(std::string_view& input, Rule& rule)
|
||||
{
|
||||
if(!parseSelectors(input, rule.selectors))
|
||||
return false;
|
||||
return parseDeclarations(input, rule.declarations);
|
||||
}
|
||||
|
||||
static RuleDataList parseStyleSheet(std::string_view input)
|
||||
{
|
||||
RuleDataList rules;
|
||||
while(!input.empty()) {
|
||||
skipOptionalSpaces(input);
|
||||
if(skipDelimiter(input, '@')) {
|
||||
int depth = 0;
|
||||
while(!input.empty()) {
|
||||
auto ch = input.front();
|
||||
input.remove_prefix(1);
|
||||
if(ch == ';' && depth == 0)
|
||||
break;
|
||||
if(ch == '{') ++depth;
|
||||
else if(ch == '}' && depth > 0) {
|
||||
if(depth == 1)
|
||||
break;
|
||||
--depth;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Rule rule;
|
||||
if(!parseRule(input, rule))
|
||||
break;
|
||||
for(const auto& selector : rule.selectors) {
|
||||
size_t specificity = 0;
|
||||
for(const auto& simpleSelector : selector) {
|
||||
specificity += (simpleSelector.id == ElementID::Star) ? 0x0 : 0x1;
|
||||
for(const auto& attributeSelector : simpleSelector.attributeSelectors) {
|
||||
specificity += (attributeSelector.id == PropertyID::Id) ? 0x10000 : 0x100;
|
||||
}
|
||||
for(const auto& pseudoClassSelector : simpleSelector.pseudoClassSelectors) {
|
||||
specificity += 0x100;
|
||||
}
|
||||
}
|
||||
|
||||
rules.emplace_back(selector, rule.declarations, specificity, rules.size());
|
||||
}
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
static SelectorList parseQuerySelectors(std::string_view input)
|
||||
{
|
||||
SelectorList selectors;
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(!parseSelectors(input, selectors)
|
||||
|| !input.empty()) {
|
||||
return SelectorList();
|
||||
}
|
||||
|
||||
return selectors;
|
||||
}
|
||||
|
||||
inline void parseInlineStyle(std::string_view input, SVGElement* element)
|
||||
{
|
||||
std::string name;
|
||||
skipOptionalSpaces(input);
|
||||
while(readCSSIdentifier(input, name)) {
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, ':'))
|
||||
return;
|
||||
std::string value;
|
||||
while(!input.empty() && input.front() != ';') {
|
||||
value.push_back(input.front());
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
|
||||
auto id = csspropertyid(name);
|
||||
if(id != PropertyID::Unknown)
|
||||
element->setAttribute(0x100, id, value);
|
||||
skipOptionalSpacesOrDelimiter(input, ';');
|
||||
}
|
||||
}
|
||||
|
||||
inline void removeStyleComments(std::string& value)
|
||||
{
|
||||
auto start = value.find("/*");
|
||||
while(start != std::string::npos) {
|
||||
auto end = value.find("*/", start + 2);
|
||||
value.erase(start, end - start + 2);
|
||||
start = value.find("/*");
|
||||
}
|
||||
}
|
||||
|
||||
inline bool decodeText(std::string_view input, std::string& output)
|
||||
{
|
||||
output.clear();
|
||||
while(!input.empty()) {
|
||||
auto ch = input.front();
|
||||
input.remove_prefix(1);
|
||||
if(ch != '&') {
|
||||
output.push_back(ch);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipDelimiter(input, '#')) {
|
||||
int base = 10;
|
||||
if(skipDelimiter(input, 'x'))
|
||||
base = 16;
|
||||
unsigned int cp;
|
||||
if(!parseInteger(input, cp, base))
|
||||
return false;
|
||||
char c[5] = {0, 0, 0, 0, 0};
|
||||
if(cp < 0x80) {
|
||||
c[1] = 0;
|
||||
c[0] = char(cp);
|
||||
} else if(cp < 0x800) {
|
||||
c[2] = 0;
|
||||
c[1] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[0] = char(cp | 0xC0);
|
||||
} else if(cp < 0x10000) {
|
||||
c[3] = 0;
|
||||
c[2] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[1] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[0] = char(cp | 0xE0);
|
||||
} else if(cp < 0x200000) {
|
||||
c[4] = 0;
|
||||
c[3] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[2] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[1] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[0] = char(cp | 0xF0);
|
||||
}
|
||||
|
||||
output.append(c);
|
||||
} else {
|
||||
if(skipString(input, "amp")) {
|
||||
output.push_back('&');
|
||||
} else if(skipString(input, "lt")) {
|
||||
output.push_back('<');
|
||||
} else if(skipString(input, "gt")) {
|
||||
output.push_back('>');
|
||||
} else if(skipString(input, "quot")) {
|
||||
output.push_back('\"');
|
||||
} else if(skipString(input, "apos")) {
|
||||
output.push_back('\'');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!skipDelimiter(input, ';')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr bool IS_STARTNAMECHAR(int c) { return IS_ALPHA(c) || c == '_' || c == ':'; }
|
||||
constexpr bool IS_NAMECHAR(int c) { return IS_STARTNAMECHAR(c) || IS_NUM(c) || c == '-' || c == '.'; }
|
||||
|
||||
inline bool readIdentifier(std::string_view& input, std::string& output)
|
||||
{
|
||||
if(input.empty() || !IS_STARTNAMECHAR(input.front()))
|
||||
return false;
|
||||
output.clear();
|
||||
do {
|
||||
output.push_back(input.front());
|
||||
input.remove_prefix(1);
|
||||
} while(!input.empty() && IS_NAMECHAR(input.front()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Document::parse(const char* data, size_t length)
|
||||
{
|
||||
std::string buffer;
|
||||
std::string styleSheet;
|
||||
SVGElement* currentElement = nullptr;
|
||||
int ignoring = 0;
|
||||
auto handleText = [&](std::string_view text, bool in_cdata) {
|
||||
if(text.empty() || currentElement == nullptr || ignoring > 0)
|
||||
return;
|
||||
if(currentElement->id() != ElementID::Text && currentElement->id() != ElementID::Tspan && currentElement->id() != ElementID::Style) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(in_cdata) {
|
||||
buffer.assign(text);
|
||||
} else {
|
||||
decodeText(text, buffer);
|
||||
}
|
||||
|
||||
if(currentElement->id() == ElementID::Style) {
|
||||
removeStyleComments(buffer);
|
||||
styleSheet.append(buffer);
|
||||
} else {
|
||||
auto node = std::make_unique<SVGTextNode>(this);
|
||||
node->setData(buffer);
|
||||
currentElement->addChild(std::move(node));
|
||||
}
|
||||
};
|
||||
|
||||
std::string_view input(data, length);
|
||||
if(length >= 3) {
|
||||
auto buffer = (const uint8_t*)(data);
|
||||
|
||||
const auto c1 = buffer[0];
|
||||
const auto c2 = buffer[1];
|
||||
const auto c3 = buffer[2];
|
||||
if(c1 == 0xEF && c2 == 0xBB && c3 == 0xBF) {
|
||||
input.remove_prefix(3);
|
||||
}
|
||||
}
|
||||
|
||||
while(!input.empty()) {
|
||||
if(currentElement) {
|
||||
auto text = input.substr(0, input.find('<'));
|
||||
handleText(text, false);
|
||||
input.remove_prefix(text.length());
|
||||
} else {
|
||||
if(!skipOptionalSpaces(input)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!skipDelimiter(input, '<'))
|
||||
return false;
|
||||
if(skipDelimiter(input, '?')) {
|
||||
if(!readIdentifier(input, buffer))
|
||||
return false;
|
||||
auto n = input.find("?>");
|
||||
if(n == std::string_view::npos)
|
||||
return false;
|
||||
input.remove_prefix(n + 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipDelimiter(input, '!')) {
|
||||
if(skipString(input, "--")) {
|
||||
auto n = input.find("-->");
|
||||
if(n == std::string_view::npos)
|
||||
return false;
|
||||
handleText(input.substr(0, n), false);
|
||||
input.remove_prefix(n + 3);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipString(input, "[CDATA[")) {
|
||||
auto n = input.find("]]>");
|
||||
if(n == std::string_view::npos)
|
||||
return false;
|
||||
handleText(input.substr(0, n), true);
|
||||
input.remove_prefix(n + 3);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipString(input, "DOCTYPE")) {
|
||||
while(!input.empty() && input.front() != '>') {
|
||||
if(input.front() == '[') {
|
||||
int depth = 1;
|
||||
input.remove_prefix(1);
|
||||
while(!input.empty() && depth > 0) {
|
||||
if(input.front() == '[') ++depth;
|
||||
else if(input.front() == ']') --depth;
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
} else {
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
}
|
||||
|
||||
if(!skipDelimiter(input, '>'))
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(skipDelimiter(input, '/')) {
|
||||
if(currentElement == nullptr && ignoring == 0)
|
||||
return false;
|
||||
if(!readIdentifier(input, buffer))
|
||||
return false;
|
||||
if(ignoring == 0) {
|
||||
auto id = elementid(buffer);
|
||||
if(id != currentElement->id())
|
||||
return false;
|
||||
currentElement = currentElement->parentElement();
|
||||
} else {
|
||||
--ignoring;
|
||||
}
|
||||
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, '>'))
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!readIdentifier(input, buffer))
|
||||
return false;
|
||||
SVGElement* element = nullptr;
|
||||
if(ignoring > 0) {
|
||||
++ignoring;
|
||||
} else {
|
||||
auto id = elementid(buffer);
|
||||
if(id == ElementID::Unknown) {
|
||||
ignoring = 1;
|
||||
} else {
|
||||
if(m_rootElement && currentElement == nullptr)
|
||||
return false;
|
||||
if(m_rootElement == nullptr) {
|
||||
if(id != ElementID::Svg)
|
||||
return false;
|
||||
m_rootElement = std::make_unique<SVGRootElement>(this);
|
||||
element = m_rootElement.get();
|
||||
} else {
|
||||
auto child = SVGElement::create(this, id);
|
||||
element = child.get();
|
||||
currentElement->addChild(std::move(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
skipOptionalSpaces(input);
|
||||
while(readIdentifier(input, buffer)) {
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, '='))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
if(input.empty() || !(input.front() == '\"' || input.front() == '\''))
|
||||
return false;
|
||||
auto quote = input.front();
|
||||
input.remove_prefix(1);
|
||||
auto n = input.find(quote);
|
||||
if(n == std::string_view::npos)
|
||||
return false;
|
||||
auto id = PropertyID::Unknown;
|
||||
if(element != nullptr)
|
||||
id = propertyid(buffer);
|
||||
if(id != PropertyID::Unknown) {
|
||||
decodeText(input.substr(0, n), buffer);
|
||||
if(id == PropertyID::Style) {
|
||||
removeStyleComments(buffer);
|
||||
parseInlineStyle(buffer, element);
|
||||
} else {
|
||||
if(id == PropertyID::Id)
|
||||
m_rootElement->addElementById(buffer, element);
|
||||
element->setAttribute(0x1, id, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
input.remove_prefix(n + 1);
|
||||
skipOptionalSpaces(input);
|
||||
}
|
||||
|
||||
if(skipDelimiter(input, '>')) {
|
||||
if(element != nullptr)
|
||||
currentElement = element;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipDelimiter(input, '/')) {
|
||||
if(!skipDelimiter(input, '>'))
|
||||
return false;
|
||||
if(ignoring > 0)
|
||||
--ignoring;
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(m_rootElement == nullptr || ignoring > 0 || !input.empty())
|
||||
return false;
|
||||
applyStyleSheet(styleSheet);
|
||||
m_rootElement->build();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Document::applyStyleSheet(const std::string& content)
|
||||
{
|
||||
auto rules = parseStyleSheet(content);
|
||||
if(!rules.empty()) {
|
||||
std::sort(rules.begin(), rules.end());
|
||||
m_rootElement->transverse([&rules](SVGElement* element) {
|
||||
for(const auto& rule : rules) {
|
||||
if(rule.match(element)) {
|
||||
for(const auto& declaration : rule.declarations()) {
|
||||
element->setAttribute(declaration.specificity, declaration.id, declaration.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ElementList Document::querySelectorAll(const std::string& content) const
|
||||
{
|
||||
auto selectors = parseQuerySelectors(content);
|
||||
if(selectors.empty())
|
||||
return ElementList();
|
||||
ElementList elements;
|
||||
m_rootElement->transverse([&](SVGElement* element) {
|
||||
for(const auto& selector : selectors) {
|
||||
if(matchSelector(selector, element)) {
|
||||
elements.push_back(element);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
Reference in New Issue
Block a user