File indexing completed on 2025-01-05 04:01:18
0001 /* 0002 * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 0007 #pragma once 0008 0009 #include "detail.hpp" 0010 0011 #include <algorithm> 0012 0013 #include <QDomDocument> 0014 0015 0016 namespace glaxnimate::io::svg::detail { 0017 0018 class CssSelector 0019 { 0020 public: 0021 void set_tag(const QString& tag) 0022 { 0023 if ( tag != '*' && this->tag.isEmpty() ) 0024 specificity += 1; 0025 this->tag = tag; 0026 } 0027 0028 void set_id(const QString& id) 0029 { 0030 if ( this->id.isEmpty() ) 0031 specificity += 100; 0032 this->id = id; 0033 } 0034 0035 void add_class(const QString& class_name) 0036 { 0037 classes.push_back(class_name); 0038 specificity += 10; 0039 } 0040 0041 bool match(const QDomElement& element, const std::unordered_set<QString>& class_names) const 0042 { 0043 if ( !tag.isEmpty() && tag != "*" && tag != element.tagName() ) 0044 return false; 0045 0046 if ( !id.isEmpty() && id != element.attribute("id") ) 0047 return false; 0048 0049 for ( const auto& class_name : classes ) 0050 { 0051 if ( class_names.count(class_name) == 0 ) 0052 return false; 0053 } 0054 0055 if ( !rule.isEmpty() ) 0056 return false; 0057 0058 return true; 0059 } 0060 0061 bool empty() const 0062 { 0063 return tag.isEmpty() && id.isEmpty() && classes.empty() && rule.isEmpty(); 0064 } 0065 0066 void set_at_rule(const QString& rule) 0067 { 0068 this->rule = rule; 0069 } 0070 0071 const QString& at_rule() const 0072 { 0073 return this->rule; 0074 } 0075 0076 private: 0077 int specificity = 0; 0078 QString tag; 0079 QString id; 0080 QStringList classes; 0081 QString rule; 0082 friend struct CssStyleBlock; 0083 }; 0084 0085 struct CssStyleBlock 0086 { 0087 CssSelector selector; 0088 Style::Map style; 0089 0090 void merge_into(Style& output) const 0091 { 0092 for ( const auto& p : style ) 0093 output[p.first] = p.second; 0094 } 0095 0096 bool operator<(const CssStyleBlock& other) const 0097 { 0098 return selector.specificity < other.selector.specificity; 0099 } 0100 }; 0101 0102 0103 class CssParser 0104 { 0105 public: 0106 CssParser(std::vector<CssStyleBlock>& blocks) 0107 : blocks(blocks) 0108 { 0109 } 0110 0111 void parse(const QString& css) 0112 { 0113 data = css; 0114 index = -1; 0115 0116 parse_selector(); 0117 } 0118 0119 0120 private: 0121 enum class TokenType 0122 { 0123 SelectorTag, 0124 SelectorClass, 0125 SelectorId, 0126 SelectorOther, 0127 SelectorComma, 0128 SelectorAt, 0129 0130 BlockBegin, 0131 BlockEnd, 0132 0133 RuleName, 0134 RuleColon, 0135 RuleArg, 0136 RuleSemicolon, 0137 0138 Eof, 0139 }; 0140 0141 using Token = std::pair<TokenType, QString>; 0142 0143 QChar next_ch_raw() 0144 { 0145 ++index; 0146 0147 if ( eof() ) 0148 return {}; 0149 0150 return data[index]; 0151 } 0152 0153 bool eof() const 0154 { 0155 return index >= data.size(); 0156 } 0157 0158 void back() 0159 { 0160 if ( !eof() ) 0161 --index; 0162 } 0163 0164 QChar next_ch() 0165 { 0166 QChar c = next_ch_raw(); 0167 0168 // Skip comments 0169 if ( c == '/' ) 0170 { 0171 QChar d = next_ch_raw(); 0172 if ( d == '*' ) 0173 { 0174 while ( true ) 0175 { 0176 d = next_ch_raw(); 0177 0178 if ( eof() ) 0179 return {}; 0180 0181 if ( d == '*' ) 0182 { 0183 d = next_ch_raw(); 0184 // Treat comments as spaces 0185 if ( d == '/' ) 0186 return ' '; 0187 back(); 0188 } 0189 } 0190 } 0191 else 0192 { 0193 back(); 0194 } 0195 } 0196 0197 return c; 0198 } 0199 0200 0201 static bool is_identifier_start(const QChar& ch) 0202 { 0203 return ch.isLetter() || ch == '_' || ch == '-'; 0204 } 0205 0206 static bool is_identifier(const QChar& ch) 0207 { 0208 return is_identifier_start(ch) || ch.isNumber(); 0209 } 0210 0211 QString lex_identifier() 0212 { 0213 QString id; 0214 QChar ch; 0215 0216 while ( true ) 0217 { 0218 ch = next_ch(); 0219 if ( is_identifier(ch) ) 0220 id += ch; 0221 else 0222 break; 0223 } 0224 0225 back(); 0226 0227 return id; 0228 } 0229 0230 QString lex_at_selector() 0231 { 0232 QString id = "@"; 0233 QChar ch; 0234 0235 while ( true ) 0236 { 0237 ch = next_ch(); 0238 if ( ch == '{' || ch == ',' ) 0239 break; 0240 else 0241 id += ch; 0242 } 0243 0244 back(); 0245 0246 return id.trimmed(); 0247 } 0248 0249 Token lex_selector() 0250 { 0251 QChar ch = next_ch(); 0252 if ( eof() ) 0253 return {TokenType::Eof, {}}; 0254 0255 if ( is_identifier_start(ch) ) 0256 return {TokenType::SelectorTag, ch + lex_identifier()}; 0257 else if ( ch == '#' ) 0258 return {TokenType::SelectorId, lex_identifier()}; 0259 else if ( ch == '.' ) 0260 return {TokenType::SelectorClass, lex_identifier()}; 0261 else if ( ch == ',' ) 0262 return {TokenType::SelectorComma, {}}; 0263 else if ( ch == '{' ) 0264 return {TokenType::BlockBegin, {}}; 0265 else if ( ch == '*' ) 0266 return {TokenType::SelectorTag, ch}; 0267 else if ( ch == '@' ) 0268 return {TokenType::SelectorAt, lex_at_selector()}; 0269 0270 if ( ch.isSpace() ) 0271 { 0272 skip_space(); 0273 ch = next_ch(); 0274 if ( ch == ',' ) 0275 return {TokenType::SelectorComma, {}}; 0276 if ( ch == '{' ) 0277 return {TokenType::BlockBegin, {}}; 0278 back(); 0279 } 0280 0281 return {TokenType::SelectorOther, {}}; 0282 } 0283 0284 Token ignore_selector() 0285 { 0286 Token token = {TokenType::Eof, {}}; 0287 0288 do 0289 { 0290 token = lex_selector(); 0291 if ( token.first == TokenType::SelectorComma ) 0292 return lex_selector(); 0293 } 0294 while ( token.first != TokenType::Eof && token.first != TokenType::BlockBegin ); 0295 0296 return token; 0297 } 0298 0299 void skip_space() 0300 { 0301 QChar c; 0302 do 0303 { 0304 c = next_ch(); 0305 } 0306 while ( !eof() && c.isSpace() ); 0307 0308 back(); 0309 } 0310 0311 void ignore_block() 0312 { 0313 Token token = {TokenType::Eof, {}}; 0314 0315 do 0316 { 0317 token = lex_selector(); 0318 } 0319 while ( token.first != TokenType::Eof && token.first != TokenType::BlockEnd ); 0320 } 0321 0322 bool parse_selector_step(const Token& token) 0323 { 0324 if ( token.first == TokenType::SelectorClass ) 0325 selectors.back().add_class(token.second); 0326 else if ( token.first == TokenType::SelectorId ) 0327 selectors.back().set_id(token.second); 0328 else if ( token.first == TokenType::SelectorTag ) 0329 selectors.back().set_tag(token.second); 0330 else if ( token.first == TokenType::SelectorAt ) 0331 selectors.back().set_at_rule(token.second); 0332 else 0333 return false; 0334 0335 return true; 0336 } 0337 0338 void parse_selector() 0339 { 0340 while ( true ) 0341 { 0342 skip_space(); 0343 selectors.clear(); 0344 Token token = lex_selector(); 0345 0346 if ( token.first == TokenType::BlockBegin ) 0347 { 0348 ignore_block(); 0349 token = lex_selector(); 0350 } 0351 0352 while ( true ) 0353 { 0354 selectors.push_back({}); 0355 0356 while ( parse_selector_step(token) ) 0357 token = lex_selector(); 0358 0359 if ( eof() ) 0360 return; 0361 0362 if ( token.first == TokenType::BlockBegin ) 0363 { 0364 if ( selectors.back().empty() ) 0365 selectors.pop_back(); 0366 break; 0367 } 0368 0369 if ( token.first != TokenType::SelectorComma ) 0370 { 0371 token = ignore_selector(); 0372 selectors.pop_back(); 0373 } 0374 else 0375 { 0376 skip_space(); 0377 token = lex_selector(); 0378 } 0379 } 0380 0381 if ( selectors.empty() ) 0382 ignore_block(); 0383 else 0384 parse_block(); 0385 } 0386 } 0387 0388 Token lex_rule() 0389 { 0390 skip_space(); 0391 0392 QChar ch = next_ch(); 0393 if ( eof() ) 0394 return {TokenType::Eof, {}}; 0395 0396 if ( is_identifier_start(ch) ) 0397 return {TokenType::RuleName, ch + lex_identifier()}; 0398 else if ( ch == ':' ) 0399 return {TokenType::RuleColon, {}}; 0400 else if ( ch == ';' ) 0401 return {TokenType::RuleSemicolon, {}}; 0402 else if ( ch == '}' ) 0403 return {TokenType::BlockEnd, {}}; 0404 0405 return {TokenType::RuleArg, ch}; 0406 0407 } 0408 0409 Token ignore_rule() 0410 { 0411 Token token = lex_rule(); 0412 while ( token.first != TokenType::Eof && token.first != TokenType::RuleSemicolon && token.first != TokenType::BlockEnd ) 0413 token = lex_rule(); 0414 return token; 0415 } 0416 0417 void lex_quoted_string(QString& value, QChar terminator) 0418 { 0419 while ( true ) 0420 { 0421 QChar ch = next_ch(); 0422 0423 if ( eof() ) 0424 break; 0425 0426 value += ch; 0427 0428 if ( ch == terminator ) 0429 break; 0430 0431 if ( ch == '\\' ) 0432 { 0433 ch = next_ch(); 0434 if ( eof() ) 0435 break; 0436 value += ch; 0437 } 0438 } 0439 } 0440 0441 Token lex_rule_value(QString& value) 0442 { 0443 if ( value == "\"" || value == "'" ) 0444 lex_quoted_string(value, value[0]); 0445 0446 while ( true ) 0447 { 0448 QChar ch = next_ch(); 0449 if ( eof() ) 0450 return {TokenType::Eof, {}}; 0451 else if ( ch == ';' ) 0452 return {TokenType::RuleSemicolon, {}}; 0453 else if ( ch == '}' ) 0454 return {TokenType::BlockEnd, {}}; 0455 0456 value += ch; 0457 if ( ch == '"' || ch == '\'' ) 0458 { 0459 lex_quoted_string(value, ch); 0460 } 0461 } 0462 } 0463 0464 void parse_block() 0465 { 0466 rules.clear(); 0467 0468 while ( true ) 0469 { 0470 Token token = lex_rule(); 0471 if ( eof() || token.first == TokenType::BlockEnd ) 0472 break; 0473 0474 if ( token.first != TokenType::RuleName ) 0475 { 0476 ignore_rule(); 0477 continue; 0478 } 0479 0480 QString name = token.second; 0481 0482 if ( lex_rule().first != TokenType::RuleColon ) 0483 { 0484 ignore_rule(); 0485 continue; 0486 } 0487 0488 token = lex_rule(); 0489 if ( eof() || token.first == TokenType::BlockEnd ) 0490 break; 0491 if ( token.first == TokenType::RuleSemicolon ) 0492 continue; 0493 0494 QString value = token.second; 0495 0496 token = lex_rule_value(value); 0497 if ( !value.isEmpty() ) 0498 rules[name] = value.trimmed(); 0499 0500 if ( eof() || token.first == TokenType::BlockEnd ) 0501 break; 0502 } 0503 0504 for ( const auto& selector : selectors ) 0505 blocks.push_back({selector, rules}); 0506 0507 rules.clear(); 0508 selectors.clear(); 0509 } 0510 0511 QString data; 0512 int index = 0; 0513 std::vector<CssStyleBlock>& blocks; 0514 std::vector<CssSelector> selectors; 0515 Style::Map rules; 0516 }; 0517 0518 } // namespace glaxnimate::io::svg::detail