#include "css.h"
#include "css.hxx"
-#include "css_style.hxx"
+#include "contrib/robin-hood/robin_hood.h"
#include "css_parser.hxx"
#define DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
#define DOCTEST_CONFIG_IMPLEMENT
{
auto parse_res = rspamd::css::parse_css(pool, {(const char* )begin, len});
-#if 0
- /* Return once semantical parsing is done */
if (parse_res.has_value()) {
return reinterpret_cast<rspamd_css>(parse_res.value().release());
}
"parse error");
return nullptr;
}
-#else
- return nullptr;
-#endif
}
namespace rspamd::css {
INIT_LOG_MODULE_PUBLIC(css);
class css_style_sheet::impl {
-
+public:
+ using selector_ptr = std::unique_ptr<css_selector>;
+ using selectors_hash = robin_hood::unordered_flat_map<selector_ptr, css_declarations_block_ptr>;
+ using universal_selector_t = std::pair<selector_ptr, css_declarations_block_ptr>;
+ selectors_hash tags_selector;
+ selectors_hash class_selectors;
+ selectors_hash id_selectors;
+ std::optional<universal_selector_t> universal_selector;
};
-css_style_sheet::css_style_sheet () : pimpl(new impl) {}
-css_style_sheet::~css_style_sheet () {}
+css_style_sheet::css_style_sheet(rspamd_mempool_t *pool)
+ : pool(pool), pimpl(new impl) {}
+css_style_sheet::~css_style_sheet() {}
+
+auto
+css_style_sheet::add_selector_rule(std::unique_ptr<css_selector> &&selector,
+ css_declarations_block_ptr decls) -> void
+{
+ impl::selectors_hash *target_hash = nullptr;
+
+ switch(selector->type) {
+ case css_selector::selector_type::SELECTOR_ALL:
+ if (pimpl->universal_selector) {
+ /* Another universal selector */
+ msg_debug_css("redefined universal selector, merging rules");
+ pimpl->universal_selector->second->merge_block(*decls);
+ }
+ else {
+ msg_debug_css("added universal selector");
+ pimpl->universal_selector = std::make_pair(std::move(selector),
+ decls);
+ }
+ break;
+ case css_selector::selector_type::SELECTOR_CLASS:
+ target_hash = &pimpl->class_selectors;
+ break;
+ case css_selector::selector_type::SELECTOR_ID:
+ target_hash = &pimpl->id_selectors;
+ break;
+ case css_selector::selector_type::SELECTOR_ELEMENT:
+ target_hash = &pimpl->tags_selector;
+ break;
+ }
+
+ if (target_hash) {
+ auto found_it = target_hash->find(selector);
+
+ if (found_it == target_hash->end()) {
+ /* Easy case, new element */
+ target_hash->insert({std::move(selector), decls});
+ }
+ else {
+ /* The problem with merging is actually in how to handle selectors chains
+ * For example, we have 2 selectors:
+ * 1. class id tag -> meaning that we first match class, then we ensure that
+ * id is also the same and finally we check the tag
+ * 2. tag class id -> it means that we check first tag, then class and then id
+ * So we have somehow equal path in the xpath terms.
+ * I suppose now, that we merely check parent stuff and handle duplicates
+ * merging when finally resolving paths.
+ */
+ auto sel_str = selector->to_string().value_or("unknown");
+ msg_debug_css("found duplicate selector: %*s", (int)sel_str.size(),
+ sel_str.data());
+ found_it->second->merge_block(*decls);
+ }
+ }
+}
}
\ No newline at end of file
return false;
}
- style_object = std::make_unique<css_style_sheet>();
+ style_object = std::make_unique<css_style_sheet>(pool);
for (auto &&rule : rules) {
/*
msg_debug_css("processed %d rules",
(int)declarations_vec->get_rules().size());
+ for (auto &&selector : selectors_vec) {
+ style_object->add_selector_rule(std::move(selector),
+ declarations_vec);
+ }
}
}
}
* Wrapper for the parser
*/
auto parse_css(rspamd_mempool_t *pool, const std::string_view &st) ->
- bool
+ tl::expected<std::unique_ptr<css_style_sheet>, css_parse_error>
{
css_parser parser(pool);
if (parser.consume_input(st)) {
- return true;
+ return parser.get_object_maybe();
}
- return false;
+ return tl::make_unexpected(css_parse_error{css_parse_error_type::PARSE_ERROR_INVALID_SYNTAX,
+ "cannot parse input"});
}
TEST_SUITE("css parser") {
return ret; /* copy elision */
}
+auto
+css_declarations_block::merge_block(const css_declarations_block &other, merge_type how)
+ -> void
+{
+ const auto &other_rules = other.get_rules();
+
+ for (const auto &rule : other_rules) {
+ auto &&found_it = rules.find(rule);
+
+ if (found_it != rules.end()) {
+ /* Duplicate, need to merge */
+ switch(how) {
+ case merge_type::merge_override:
+ /* Override */
+ rules.insert(rule);
+ break;
+ case merge_type::merge_duplicate:
+ /* Merge values */
+ (*found_it)->merge_values(*rule);
+ break;
+ case merge_type::merge_parent:
+ /* Do not merge parent rule if more specific local one is presented */
+ break;
+ }
+ }
+ else {
+ /* New property, just insert */
+ rules.insert(rule);
+ }
+ }
+}
+
void css_rule::add_value(const css_value &value)
{
values.push_back(value);