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++
+
+ - item
+ - item
+
+ - item
+ - item
+ - item
+
+ - item
+ - item
+ - item
+
+
+ - item
+
+
+ - item
+ - item
+
+
+
+ - item
+ - item
+
+
+
+
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