diff --git a/include/litehtml/element.h b/include/litehtml/element.h index e330b5c01..c09fa2b3c 100644 --- a/include/litehtml/element.h +++ b/include/litehtml/element.h @@ -35,6 +35,10 @@ namespace litehtml virtual void select_all(const css_selector& selector, elements_list& res); element::ptr _add_before_after(int type, const style& style); + + private: + std::map m_counter_values; + public: explicit element(const std::shared_ptr& doc); virtual ~element() = default; @@ -142,6 +146,16 @@ namespace litehtml { return _add_before_after(1, style); } + + string get_counter_value(const string& counter_name); + string get_counters_value(const string_vector& parameters); + void increment_counter(const string_id& counter_name_id, const int increment = 1); + void reset_counter(const string_id& counter_name_id, const int value = 0); + + private: + std::vector get_siblings_before() const; + bool find_counter(const string_id& counter_name_id, std::map::iterator& map_iterator); + void parse_counter_tokens(const string_vector& tokens, const int default_value, std::function handler) const; }; ////////////////////////////////////////////////////////////////////////// diff --git a/include/litehtml/html.h b/include/litehtml/html.h index b184ac30a..818d5ce56 100644 --- a/include/litehtml/html.h +++ b/include/litehtml/html.h @@ -23,7 +23,7 @@ namespace litehtml { - void trim(string &s); + void trim(string &s, const string& chars_to_trim = " \n\r\t"); void lcase(string &s); int value_index(const string& val, const string& strings, int defValue = -1, char delim = ';'); string index_value(int index, const string& strings, char delim = ';'); @@ -36,7 +36,9 @@ namespace litehtml int t_strcasecmp(const char *s1, const char *s2); int t_strncasecmp(const char *s1, const char *s2, size_t n); - + + bool is_number(const string& string, const bool allow_dot = 1); + inline int t_isdigit(int c) { return (c >= '0' && c <= '9'); diff --git a/include/litehtml/html_tag.h b/include/litehtml/html_tag.h index 9148031b2..9578d383f 100644 --- a/include/litehtml/html_tag.h +++ b/include/litehtml/html_tag.h @@ -119,6 +119,10 @@ namespace litehtml string get_list_marker_text(int index); element::ptr get_element_before(const style& style, bool create); element::ptr get_element_after(const style& style, bool create); + + private: + void handle_counter_properties(); + }; /************************************************************************/ diff --git a/include/litehtml/string_id.h b/include/litehtml/string_id.h index 60d30f5e4..dbde2f4aa 100644 --- a/include/litehtml/string_id.h +++ b/include/litehtml/string_id.h @@ -288,7 +288,10 @@ STRING_ID( _flex_shrink_, _flex_basis_, - _caption_side_, + _counter_reset_, + _counter_increment_, + + _caption_side_, ); #undef STRING_ID extern const string_id empty_id; // _id("") diff --git a/src/el_before_after.cpp b/src/el_before_after.cpp index 8c24eec98..c7c2d680b 100644 --- a/src/el_before_after.cpp +++ b/src/el_before_after.cpp @@ -133,7 +133,7 @@ void litehtml::el_before_after_base::add_text( const string& txt ) void litehtml::el_before_after_base::add_function( const string& fnc, const string& params ) { - int idx = value_index(fnc, "attr;counter;url"); + int idx = value_index(fnc, "attr;counter;counters;url"); switch(idx) { // attr @@ -155,9 +155,18 @@ void litehtml::el_before_after_base::add_function( const string& fnc, const stri break; // counter case 1: + add_text(get_counter_value(params)); break; - // url + // counters case 2: + { + string_vector tokens; + split_string(params, tokens, ","); + add_text(get_counters_value(tokens)); + } + break; + // url + case 3: { string p_url = params; trim(p_url); diff --git a/src/element.cpp b/src/element.cpp index 1d0a509b1..5fa68cac6 100644 --- a/src/element.cpp +++ b/src/element.cpp @@ -288,6 +288,122 @@ bool element::is_block_formatting_context() const return false; } +litehtml::string litehtml::element::get_counter_value(const string& counter_name) +{ + std::map::iterator i; + if (find_counter(_id(counter_name), i)) + { + return std::to_string(i->second); + } + return "0"; +} + +string litehtml::element::get_counters_value(const string_vector& parameters) +{ + string result = ""; + if (parameters.size() >= 2) { + const string_id counter_name_id = _id(parameters[0]); + string delims = parameters[1]; + litehtml::trim(delims, "\"'"); + + string_vector values; + + element::ptr current = shared_from_this(); + while (current != nullptr) + { + auto map_iterator = current->m_counter_values.find(counter_name_id); + if (map_iterator != current->m_counter_values.end()) { + values.push_back(std::to_string(map_iterator->second)); + } + current = current->parent(); + } + if (values.empty()) { + // if no counter is found, instanciate one with value '0' + shared_from_this()->m_counter_values[counter_name_id] = 0; + result = "0"; + } + else { + std::reverse(values.begin(), values.end()); + litehtml::join_string(result, values, delims); + } + } + return result; +} + + +bool litehtml::element::find_counter(const string_id& counter_name_id, std::map::iterator& map_iterator) { + element::ptr current = shared_from_this(); + + while (current != nullptr) + { + map_iterator = current->m_counter_values.find(counter_name_id); + if (map_iterator != current->m_counter_values.end()) { + return true; + } + + // on each level, search previous siblings too + std::vector siblings = current->get_siblings_before(); + std::reverse(siblings.begin(), siblings.end()); + for (const element::ptr& sibling : siblings) { + map_iterator = sibling->m_counter_values.find(counter_name_id); + if (map_iterator != sibling->m_counter_values.end()) { + return true; + } + } + current = current->parent(); + } + + return false; +} + +std::vector litehtml::element::get_siblings_before() const +{ + std::vector siblings; + if (parent() != nullptr) { + for (const element::ptr& sybling : parent()->children()) { + if (sybling == shared_from_this()) { + break; + } + siblings.push_back(sybling); + } + } + return siblings; +} + + +void litehtml::element::parse_counter_tokens(const string_vector& tokens, const int default_value, std::function handler) const { + int pos = 0; + while (pos < tokens.size()) { + string name = tokens[pos]; + int value = default_value; + if (pos < tokens.size() - 1 && litehtml::is_number(tokens[pos + 1], 0)) { + value = atoi(tokens[pos + 1].c_str()); + pos += 2; + } + else { + pos += 1; + } + handler(_id(name), value); + } +} + +void litehtml::element::increment_counter(const string_id& counter_name_id, const int increment) +{ + std::map::iterator i; + if (find_counter(counter_name_id, i)) { + i->second = i->second + increment; + } + else { + // if counter is not found, initialize one on this element + m_counter_values[counter_name_id] = increment; + } +} + +void litehtml::element::reset_counter(const string_id& counter_name_id, const int value) +{ + m_counter_values[counter_name_id] = value; +} + const background* element::get_background(bool own_only) LITEHTML_RETURN_FUNC(nullptr) void element::add_style( const style& style) LITEHTML_EMPTY_FUNC void element::select_all(const css_selector& selector, elements_list& res) LITEHTML_EMPTY_FUNC diff --git a/src/html.cpp b/src/html.cpp index 9695180a7..ee8ddad8d 100644 --- a/src/html.cpp +++ b/src/html.cpp @@ -2,9 +2,9 @@ #include "types.h" #include "utf8_strings.h" -void litehtml::trim(string &s) +void litehtml::trim(string &s, const string& chars_to_trim) { - string::size_type pos = s.find_first_not_of(" \n\r\t"); + string::size_type pos = s.find_first_not_of(chars_to_trim); if(pos != string::npos) { s.erase(s.begin(), s.begin() + pos); @@ -14,7 +14,7 @@ void litehtml::trim(string &s) s = ""; return; } - pos = s.find_last_not_of(" \n\r\t"); + pos = s.find_last_not_of(chars_to_trim); if(pos != string::npos) { s.erase(s.begin() + pos + 1, s.end()); @@ -277,3 +277,14 @@ litehtml::string litehtml::get_escaped_string(const string& in_str) } return ret; } + +bool litehtml::is_number(const string& string, const bool allow_dot) { + for (auto ch : string) + { + if (!(t_isdigit(ch) || (allow_dot && ch == '.'))) + { + return false; + } + } + return true; +} diff --git a/src/html_tag.cpp b/src/html_tag.cpp index 0b978f56a..ab2c45e99 100644 --- a/src/html_tag.cpp +++ b/src/html_tag.cpp @@ -1548,9 +1548,33 @@ litehtml::element::ptr litehtml::html_tag::get_element_after(const style& style, return nullptr; } + +void litehtml::html_tag::handle_counter_properties() +{ + const auto& reset_property = m_style.get_property(string_id::_counter_reset_); + if (reset_property.m_type == prop_type_string_vector) { + auto reset_function = [&](const string_id&name_id, const int value) { + reset_counter(name_id, value); + }; + parse_counter_tokens(reset_property.m_string_vector, 0, reset_function); + return; + } + + const auto& inc_property = m_style.get_property(string_id::_counter_increment_); + if (inc_property.m_type == prop_type_string_vector) { + auto inc_function = [&](const string_id&name_id, const int value) { + increment_counter(name_id, value); + }; + parse_counter_tokens(inc_property.m_string_vector, 1, inc_function); + return; + } +} + + void litehtml::html_tag::add_style(const style& style) { m_style.combine(style); + handle_counter_properties(); } void litehtml::html_tag::refresh_styles() diff --git a/src/style.cpp b/src/style.cpp index 5642d36c4..2e462f486 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -536,6 +536,15 @@ void style::add_property(string_id name, const string& val, const string& baseur add_parsed_property(_flex_basis_, property_value(length, important)); break; + case _counter_increment_: + case _counter_reset_: + { + string_vector tokens; + split_string(val, tokens, " "); + add_parsed_property(name, property_value(tokens, important)); + break; + } + default: add_parsed_property(name, property_value(val, important)); } @@ -992,18 +1001,6 @@ void style::parse_font(const string& val, bool important) void style::parse_flex(const string& val, bool important) { - auto is_number = [](const string& val) - { - for (auto ch : val) - { - if ((ch < '0' || ch > '9') && ch != '.') - { - return false; - } - } - return true; - }; - css_length _auto = css_length::predef_value(flex_basis_auto); if (val == "initial") @@ -1046,7 +1043,7 @@ void style::parse_flex(const string& val, bool important) float grow = t_strtof(tokens[0]); add_parsed_property(_flex_grow_, property_value(grow, important)); - if (is_number(tokens[1])) + if (litehtml::is_number(tokens[1])) { float shrink = t_strtof(tokens[1]); add_parsed_property(_flex_shrink_, property_value(shrink, important)); diff --git a/test/render/counter.htm b/test/render/counter.htm new file mode 100644 index 000000000..bd24d157c --- /dev/null +++ b/test/render/counter.htm @@ -0,0 +1,79 @@ + + + + + css counters + + + + +

HTML/CSS Tutorials

+

HTML

+

CSS

+

Bootstrap

+

W3.CSS

+ +

Scripting Tutorials

+

JavaScript

+

jQuery

+

React

+ +

Programming Tutorials

+

Python

+

Java

+

C++

+
    +
  1. item
  2. +
  3. item +
      +
    1. item
    2. +
    3. item
    4. +
    5. item +
        +
      1. item
      2. +
      3. item
      4. +
      5. item
      6. +
      +
    6. +
    7. item
    8. +
    +
  4. +
  5. item
  6. +
  7. item
  8. +
+ +
    +
  1. item
  2. +
  3. item
  4. +
+ + + diff --git a/test/render/counter.htm.png b/test/render/counter.htm.png new file mode 100644 index 000000000..ae151ac44 Binary files /dev/null and b/test/render/counter.htm.png differ