libhtmlpp 1.0.0
Loading...
Searching...
No Matches
css.cpp
Go to the documentation of this file.
1/*******************************************************************************
2Copyright (c) 2021, Jan Koester jan.koester@gmx.net
3All rights reserved.
4
5Redistribution and use in source and binary forms, with or without
6modification, are permitted provided that the following conditions are met:
7 * Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9 * Redistributions in binary form must reproduce the above copyright
10 notice, this list of conditions and the following disclaimer in the
11 documentation and/or other materials provided with the distribution.
12 * Neither the name of the <organization> nor the
13 names of its contributors may be used to endorse or promote products
14 derived from this software without specific prior written permission.
15
16THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
20DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26*******************************************************************************/
27
28#include "css.h"
29#include "exception.h"
30
31#include <algorithm>
32#include <sstream>
33
34namespace {
35
36 bool isWhitespace(char c) {
37 return c == ' ' || c == '\t' || c == '\n' || c == '\r';
38 }
39
40 std::string trim(const std::string &s) {
41 size_t start = 0;
42 while (start < s.size() && isWhitespace(s[start])) ++start;
43 size_t end = s.size();
44 while (end > start && isWhitespace(s[end - 1])) --end;
45 return s.substr(start, end - start);
46 }
47
48}
49
50// --- CSSProperty ---
51
53
54libhtmlpp::CSSProperty::CSSProperty(const std::string &name, const std::string &value)
55 : _Name(name), _Value(value) {}
56
58 : _Name(prop._Name), _Value(prop._Value) {}
59
61
63 if (this != &prop) {
64 _Name = prop._Name;
65 _Value = prop._Value;
66 }
67 return *this;
68}
69
70const std::string& libhtmlpp::CSSProperty::getName() const { return _Name; }
71void libhtmlpp::CSSProperty::setName(const std::string &name) { _Name = name; }
72
73const std::string& libhtmlpp::CSSProperty::getValue() const { return _Value; }
74void libhtmlpp::CSSProperty::setValue(const std::string &value) { _Value = value; }
75
76// --- CSSDeclaration ---
77
79
81 : _Properties(decl._Properties) {}
82
84
86 if (this != &decl) {
87 _Properties = decl._Properties;
88 }
89 return *this;
90}
91
92void libhtmlpp::CSSDeclaration::addProperty(const std::string &name, const std::string &value) {
93 std::string tname = trim(name);
94 std::string tvalue = trim(value);
95
96 if (tname.empty()) return;
97
98 for (auto &prop : _Properties) {
99 if (prop.getName() == tname) {
100 prop.setValue(tvalue);
101 return;
102 }
103 }
104 _Properties.emplace_back(tname, tvalue);
105}
106
107void libhtmlpp::CSSDeclaration::removeProperty(const std::string &name) {
108 std::string tname = trim(name);
109 _Properties.erase(
110 std::remove_if(_Properties.begin(), _Properties.end(),
111 [&tname](const CSSProperty &p) { return p.getName() == tname; }),
112 _Properties.end()
113 );
114}
115
117 std::string tname = trim(name);
118 for (const auto &prop : _Properties) {
119 if (prop.getName() == tname) return &prop;
120 }
121 return nullptr;
122}
123
124const std::vector<libhtmlpp::CSSProperty>& libhtmlpp::CSSDeclaration::getProperties() const {
125 return _Properties;
126}
127
129 std::string result;
130 for (size_t i = 0; i < _Properties.size(); ++i) {
131 result += _Properties[i].getName();
132 result += ": ";
133 result += _Properties[i].getValue();
134 result += ";";
135 if (i + 1 < _Properties.size()) result += " ";
136 }
137 return result;
138}
139
140void libhtmlpp::CSSDeclaration::parse(const std::string &input) {
141 _Properties.clear();
142
143 size_t pos = 0;
144 size_t len = input.size();
145
146 while (pos < len) {
147 // Skip whitespace
148 while (pos < len && isWhitespace(input[pos])) ++pos;
149 if (pos >= len) break;
150
151 // Find the colon separating property name from value
152 size_t colon = input.find(':', pos);
153 if (colon == std::string::npos) break;
154
155 std::string propName = trim(input.substr(pos, colon - pos));
156
157 pos = colon + 1;
158
159 // Find the semicolon ending this declaration
160 // Handle parentheses for functions like rgb(), url()
161 size_t valueStart = pos;
162 int parenDepth = 0;
163 bool inSingleQuote = false;
164 bool inDoubleQuote = false;
165
166 while (pos < len) {
167 char c = input[pos];
168
169 if (inSingleQuote) {
170 if (c == '\'') inSingleQuote = false;
171 ++pos;
172 continue;
173 }
174 if (inDoubleQuote) {
175 if (c == '"') inDoubleQuote = false;
176 ++pos;
177 continue;
178 }
179
180 if (c == '\'') { inSingleQuote = true; ++pos; continue; }
181 if (c == '"') { inDoubleQuote = true; ++pos; continue; }
182 if (c == '(') { ++parenDepth; ++pos; continue; }
183 if (c == ')') { if (parenDepth > 0) --parenDepth; ++pos; continue; }
184
185 if (c == ';' && parenDepth == 0) {
186 break;
187 }
188 ++pos;
189 }
190
191 std::string propValue = trim(input.substr(valueStart, pos - valueStart));
192
193 if (!propName.empty()) {
194 addProperty(propName, propValue);
195 }
196
197 if (pos < len && input[pos] == ';') ++pos;
198 }
199}
200
202 _Properties.clear();
203}
204
205// --- CSSRule ---
206
208
209libhtmlpp::CSSRule::CSSRule(const std::string &selector)
210 : _Selector(trim(selector)) {}
211
213 : _Selector(rule._Selector), _Declaration(rule._Declaration) {}
214
216
218 if (this != &rule) {
219 _Selector = rule._Selector;
220 _Declaration = rule._Declaration;
221 }
222 return *this;
223}
224
225const std::string& libhtmlpp::CSSRule::getSelector() const { return _Selector; }
226void libhtmlpp::CSSRule::setSelector(const std::string &selector) { _Selector = trim(selector); }
227
230
231std::string libhtmlpp::CSSRule::serialize(bool formatted) const {
232 std::string result;
233 if (formatted) {
234 result += _Selector + " {\n";
235 for (const auto &prop : _Declaration.getProperties()) {
236 result += " " + prop.getName() + ": " + prop.getValue() + ";\n";
237 }
238 result += "}";
239 } else {
240 result += _Selector + "{";
241 result += _Declaration.serialize();
242 result += "}";
243 }
244 return result;
245}
246
247// --- CSSStyleSheet ---
248
250
252 : _Rules(sheet._Rules) {}
253
255
257 if (this != &sheet) {
258 _Rules = sheet._Rules;
259 }
260 return *this;
261}
262
263void libhtmlpp::CSSStyleSheet::_skipWhitespace(const std::string &input, size_t &pos) const {
264 while (pos < input.size() && isWhitespace(input[pos])) ++pos;
265}
266
267void libhtmlpp::CSSStyleSheet::_skipComment(const std::string &input, size_t &pos) const {
268 if (pos + 1 < input.size() && input[pos] == '/' && input[pos + 1] == '*') {
269 pos += 2;
270 while (pos + 1 < input.size()) {
271 if (input[pos] == '*' && input[pos + 1] == '/') {
272 pos += 2;
273 return;
274 }
275 ++pos;
276 }
277 pos = input.size();
278 }
279}
280
281void libhtmlpp::CSSStyleSheet::parse(const std::string &input) {
282 _Rules.clear();
283
284 size_t pos = 0;
285 size_t len = input.size();
286
287 while (pos < len) {
288 _skipWhitespace(input, pos);
289 if (pos >= len) break;
290
291 // Skip comments
292 if (pos + 1 < len && input[pos] == '/' && input[pos + 1] == '*') {
293 _skipComment(input, pos);
294 continue;
295 }
296
297 // Handle @-rules
298 if (input[pos] == '@') {
299 size_t atStart = pos;
300
301 // Check for @-rules with blocks like @media, @keyframes, @supports, @font-face
302 // and simple @-rules like @import, @charset
303 size_t semiPos = input.find(';', pos);
304 size_t bracePos = input.find('{', pos);
305
306 if (bracePos != std::string::npos && (semiPos == std::string::npos || bracePos < semiPos)) {
307 // Block @-rule: find matching closing brace
308 std::string atSelector = trim(input.substr(atStart, bracePos - atStart));
309 pos = bracePos + 1;
310
311 int depth = 1;
312 size_t blockStart = pos;
313 while (pos < len && depth > 0) {
314 if (input[pos] == '{') ++depth;
315 else if (input[pos] == '}') --depth;
316 if (depth > 0) ++pos;
317 }
318
319 std::string blockContent = input.substr(blockStart, pos - blockStart);
320 pos = (pos < len) ? pos + 1 : pos;
321
322 // Parse nested rules inside the @-block
323 CSSStyleSheet nested;
324 nested.parse(blockContent);
325
326 // Wrap each nested rule with the @-selector
327 for (const auto &nestedRule : nested.getRules()) {
328 CSSRule rule;
329 rule.setSelector(atSelector + " " + nestedRule.getSelector());
330 for (const auto &prop : nestedRule.getDeclaration().getProperties()) {
331 rule.getDeclaration().addProperty(prop.getName(), prop.getValue());
332 }
333 _Rules.push_back(rule);
334 }
335
336 // If no nested rules (e.g. @font-face), store as single rule
337 if (nested.getRuleCount() == 0 && !blockContent.empty()) {
338 CSSRule rule(atSelector);
339 rule.getDeclaration().parse(blockContent);
340 _Rules.push_back(rule);
341 }
342 } else if (semiPos != std::string::npos) {
343 // Simple @-rule ending with semicolon (e.g. @import, @charset)
344 std::string atRule = trim(input.substr(atStart, semiPos - atStart));
345 CSSRule rule(atRule);
346 _Rules.push_back(rule);
347 pos = semiPos + 1;
348 } else {
349 // Malformed @-rule, skip
350 ++pos;
351 }
352 continue;
353 }
354
355 // Standard rule: find selector then { declarations }
356 size_t braceOpen = input.find('{', pos);
357 if (braceOpen == std::string::npos) break;
358
359 std::string selector = trim(input.substr(pos, braceOpen - pos));
360 pos = braceOpen + 1;
361
362 // Find matching closing brace
363 int depth = 1;
364 size_t declStart = pos;
365 while (pos < len && depth > 0) {
366 if (pos + 1 < len && input[pos] == '/' && input[pos + 1] == '*') {
367 _skipComment(input, pos);
368 continue;
369 }
370 if (input[pos] == '{') ++depth;
371 else if (input[pos] == '}') --depth;
372 if (depth > 0) ++pos;
373 }
374
375 std::string declarations = input.substr(declStart, pos - declStart);
376 pos = (pos < len) ? pos + 1 : pos;
377
378 if (!selector.empty()) {
379 CSSRule rule(selector);
380 rule.getDeclaration().parse(declarations);
381 _Rules.push_back(rule);
382 }
383 }
384}
385
387 _Rules.push_back(rule);
388}
389
391 if (index < _Rules.size()) {
392 _Rules.erase(_Rules.begin() + static_cast<std::ptrdiff_t>(index));
393 }
394}
395
397 if (index < _Rules.size()) return &_Rules[index];
398 return nullptr;
399}
400
402 return _Rules.size();
403}
404
405const std::vector<libhtmlpp::CSSRule>& libhtmlpp::CSSStyleSheet::getRules() const {
406 return _Rules;
407}
408
409std::string libhtmlpp::CSSStyleSheet::serialize(bool formatted) const {
410 std::string result;
411 for (size_t i = 0; i < _Rules.size(); ++i) {
412 result += _Rules[i].serialize(formatted);
413 if (formatted) result += "\n";
414 if (i + 1 < _Rules.size() && formatted) result += "\n";
415 }
416 return result;
417}
418
420 _Rules.clear();
421}
422
424 CSSDeclaration decl;
425 decl.parse(style);
426 return decl;
427}
428
void addProperty(const std::string &name, const std::string &value)
Definition css.cpp:92
void removeProperty(const std::string &name)
Definition css.cpp:107
const std::vector< CSSProperty > & getProperties() const
Definition css.cpp:124
std::string serialize() const
Definition css.cpp:128
const CSSProperty * getProperty(const std::string &name) const
Definition css.cpp:116
void parse(const std::string &input)
Definition css.cpp:140
CSSDeclaration & operator=(const CSSDeclaration &decl)
Definition css.cpp:85
void setName(const std::string &name)
Definition css.cpp:71
const std::string & getName() const
Definition css.cpp:70
void setValue(const std::string &value)
Definition css.cpp:74
CSSProperty & operator=(const CSSProperty &prop)
Definition css.cpp:62
const std::string & getValue() const
Definition css.cpp:73
CSSDeclaration & getDeclaration()
Definition css.cpp:228
std::string serialize(bool formatted=false) const
Definition css.cpp:231
void setSelector(const std::string &selector)
Definition css.cpp:226
CSSRule & operator=(const CSSRule &rule)
Definition css.cpp:217
const std::string & getSelector() const
Definition css.cpp:225
void parse(const std::string &input)
Definition css.cpp:281
const std::vector< CSSRule > & getRules() const
Definition css.cpp:405
CSSStyleSheet & operator=(const CSSStyleSheet &sheet)
Definition css.cpp:256
static CSSDeclaration parseInlineStyle(const std::string &style)
Definition css.cpp:423
std::string serialize(bool formatted=false) const
Definition css.cpp:409
const CSSRule * getRule(size_t index) const
Definition css.cpp:396
void addRule(const CSSRule &rule)
Definition css.cpp:386
void removeRule(size_t index)
Definition css.cpp:390
size_t getRuleCount() const
Definition css.cpp:401
bool isWhitespace(char c)
Definition css.cpp:36
std::string trim(const std::string &s)
Definition css.cpp:40