Commit 6ff5d1e6 authored by jan.koester's avatar jan.koester
Browse files

test

parent 52c65981
Loading
Loading
Loading
Loading
+427 −0
Original line number Diff line number Diff line
/*******************************************************************************
Copyright (c) 2021, Jan Koester jan.koester@gmx.net
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the <organization> nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/

#include "css.h"
#include "exception.h"

#include <algorithm>
#include <sstream>

namespace {

    bool isWhitespace(char c) {
        return c == ' ' || c == '\t' || c == '\n' || c == '\r';
    }

    std::string trim(const std::string &s) {
        size_t start = 0;
        while (start < s.size() && isWhitespace(s[start])) ++start;
        size_t end = s.size();
        while (end > start && isWhitespace(s[end - 1])) --end;
        return s.substr(start, end - start);
    }

}

// --- CSSProperty ---

libhtmlpp::CSSProperty::CSSProperty() {}

libhtmlpp::CSSProperty::CSSProperty(const std::string &name, const std::string &value)
    : _Name(name), _Value(value) {}

libhtmlpp::CSSProperty::CSSProperty(const CSSProperty &prop)
    : _Name(prop._Name), _Value(prop._Value) {}

libhtmlpp::CSSProperty::~CSSProperty() {}

libhtmlpp::CSSProperty& libhtmlpp::CSSProperty::operator=(const CSSProperty &prop) {
    if (this != &prop) {
        _Name  = prop._Name;
        _Value = prop._Value;
    }
    return *this;
}

const std::string& libhtmlpp::CSSProperty::getName() const { return _Name; }
void libhtmlpp::CSSProperty::setName(const std::string &name) { _Name = name; }

const std::string& libhtmlpp::CSSProperty::getValue() const { return _Value; }
void libhtmlpp::CSSProperty::setValue(const std::string &value) { _Value = value; }

// --- CSSDeclaration ---

libhtmlpp::CSSDeclaration::CSSDeclaration() {}

libhtmlpp::CSSDeclaration::CSSDeclaration(const CSSDeclaration &decl)
    : _Properties(decl._Properties) {}

libhtmlpp::CSSDeclaration::~CSSDeclaration() {}

libhtmlpp::CSSDeclaration& libhtmlpp::CSSDeclaration::operator=(const CSSDeclaration &decl) {
    if (this != &decl) {
        _Properties = decl._Properties;
    }
    return *this;
}

void libhtmlpp::CSSDeclaration::addProperty(const std::string &name, const std::string &value) {
    std::string tname  = trim(name);
    std::string tvalue = trim(value);

    if (tname.empty()) return;

    for (auto &prop : _Properties) {
        if (prop.getName() == tname) {
            prop.setValue(tvalue);
            return;
        }
    }
    _Properties.emplace_back(tname, tvalue);
}

void libhtmlpp::CSSDeclaration::removeProperty(const std::string &name) {
    std::string tname = trim(name);
    _Properties.erase(
        std::remove_if(_Properties.begin(), _Properties.end(),
            [&tname](const CSSProperty &p) { return p.getName() == tname; }),
        _Properties.end()
    );
}

const libhtmlpp::CSSProperty* libhtmlpp::CSSDeclaration::getProperty(const std::string &name) const {
    std::string tname = trim(name);
    for (const auto &prop : _Properties) {
        if (prop.getName() == tname) return &prop;
    }
    return nullptr;
}

const std::vector<libhtmlpp::CSSProperty>& libhtmlpp::CSSDeclaration::getProperties() const {
    return _Properties;
}

std::string libhtmlpp::CSSDeclaration::serialize() const {
    std::string result;
    for (size_t i = 0; i < _Properties.size(); ++i) {
        result += _Properties[i].getName();
        result += ": ";
        result += _Properties[i].getValue();
        result += ";";
        if (i + 1 < _Properties.size()) result += " ";
    }
    return result;
}

void libhtmlpp::CSSDeclaration::parse(const std::string &input) {
    _Properties.clear();

    size_t pos = 0;
    size_t len = input.size();

    while (pos < len) {
        // Skip whitespace
        while (pos < len && isWhitespace(input[pos])) ++pos;
        if (pos >= len) break;

        // Find the colon separating property name from value
        size_t colon = input.find(':', pos);
        if (colon == std::string::npos) break;

        std::string propName = trim(input.substr(pos, colon - pos));

        pos = colon + 1;

        // Find the semicolon ending this declaration
        // Handle parentheses for functions like rgb(), url()
        size_t valueStart = pos;
        int parenDepth = 0;
        bool inSingleQuote = false;
        bool inDoubleQuote = false;

        while (pos < len) {
            char c = input[pos];

            if (inSingleQuote) {
                if (c == '\'') inSingleQuote = false;
                ++pos;
                continue;
            }
            if (inDoubleQuote) {
                if (c == '"') inDoubleQuote = false;
                ++pos;
                continue;
            }

            if (c == '\'') { inSingleQuote = true; ++pos; continue; }
            if (c == '"')  { inDoubleQuote = true; ++pos; continue; }
            if (c == '(')  { ++parenDepth; ++pos; continue; }
            if (c == ')')  { if (parenDepth > 0) --parenDepth; ++pos; continue; }

            if (c == ';' && parenDepth == 0) {
                break;
            }
            ++pos;
        }

        std::string propValue = trim(input.substr(valueStart, pos - valueStart));

        if (!propName.empty()) {
            addProperty(propName, propValue);
        }

        if (pos < len && input[pos] == ';') ++pos;
    }
}

void libhtmlpp::CSSDeclaration::clear() {
    _Properties.clear();
}

// --- CSSRule ---

libhtmlpp::CSSRule::CSSRule() {}

libhtmlpp::CSSRule::CSSRule(const std::string &selector)
    : _Selector(trim(selector)) {}

libhtmlpp::CSSRule::CSSRule(const CSSRule &rule)
    : _Selector(rule._Selector), _Declaration(rule._Declaration) {}

libhtmlpp::CSSRule::~CSSRule() {}

libhtmlpp::CSSRule& libhtmlpp::CSSRule::operator=(const CSSRule &rule) {
    if (this != &rule) {
        _Selector    = rule._Selector;
        _Declaration = rule._Declaration;
    }
    return *this;
}

const std::string& libhtmlpp::CSSRule::getSelector() const { return _Selector; }
void libhtmlpp::CSSRule::setSelector(const std::string &selector) { _Selector = trim(selector); }

libhtmlpp::CSSDeclaration& libhtmlpp::CSSRule::getDeclaration() { return _Declaration; }
const libhtmlpp::CSSDeclaration& libhtmlpp::CSSRule::getDeclaration() const { return _Declaration; }

std::string libhtmlpp::CSSRule::serialize(bool formatted) const {
    std::string result;
    if (formatted) {
        result += _Selector + " {\n";
        for (const auto &prop : _Declaration.getProperties()) {
            result += "    " + prop.getName() + ": " + prop.getValue() + ";\n";
        }
        result += "}";
    } else {
        result += _Selector + "{";
        result += _Declaration.serialize();
        result += "}";
    }
    return result;
}

// --- CSSStyleSheet ---

libhtmlpp::CSSStyleSheet::CSSStyleSheet() {}

libhtmlpp::CSSStyleSheet::CSSStyleSheet(const CSSStyleSheet &sheet)
    : _Rules(sheet._Rules) {}

libhtmlpp::CSSStyleSheet::~CSSStyleSheet() {}

libhtmlpp::CSSStyleSheet& libhtmlpp::CSSStyleSheet::operator=(const CSSStyleSheet &sheet) {
    if (this != &sheet) {
        _Rules = sheet._Rules;
    }
    return *this;
}

void libhtmlpp::CSSStyleSheet::_skipWhitespace(const std::string &input, size_t &pos) const {
    while (pos < input.size() && isWhitespace(input[pos])) ++pos;
}

void libhtmlpp::CSSStyleSheet::_skipComment(const std::string &input, size_t &pos) const {
    if (pos + 1 < input.size() && input[pos] == '/' && input[pos + 1] == '*') {
        pos += 2;
        while (pos + 1 < input.size()) {
            if (input[pos] == '*' && input[pos + 1] == '/') {
                pos += 2;
                return;
            }
            ++pos;
        }
        pos = input.size();
    }
}

void libhtmlpp::CSSStyleSheet::parse(const std::string &input) {
    _Rules.clear();

    size_t pos = 0;
    size_t len = input.size();

    while (pos < len) {
        _skipWhitespace(input, pos);
        if (pos >= len) break;

        // Skip comments
        if (pos + 1 < len && input[pos] == '/' && input[pos + 1] == '*') {
            _skipComment(input, pos);
            continue;
        }

        // Handle @-rules
        if (input[pos] == '@') {
            size_t atStart = pos;

            // Check for @-rules with blocks like @media, @keyframes, @supports, @font-face
            // and simple @-rules like @import, @charset
            size_t semiPos = input.find(';', pos);
            size_t bracePos = input.find('{', pos);

            if (bracePos != std::string::npos && (semiPos == std::string::npos || bracePos < semiPos)) {
                // Block @-rule: find matching closing brace
                std::string atSelector = trim(input.substr(atStart, bracePos - atStart));
                pos = bracePos + 1;

                int depth = 1;
                size_t blockStart = pos;
                while (pos < len && depth > 0) {
                    if (input[pos] == '{') ++depth;
                    else if (input[pos] == '}') --depth;
                    if (depth > 0) ++pos;
                }

                std::string blockContent = input.substr(blockStart, pos - blockStart);
                pos = (pos < len) ? pos + 1 : pos;

                // Parse nested rules inside the @-block
                CSSStyleSheet nested;
                nested.parse(blockContent);

                // Wrap each nested rule with the @-selector
                for (const auto &nestedRule : nested.getRules()) {
                    CSSRule rule;
                    rule.setSelector(atSelector + " " + nestedRule.getSelector());
                    for (const auto &prop : nestedRule.getDeclaration().getProperties()) {
                        rule.getDeclaration().addProperty(prop.getName(), prop.getValue());
                    }
                    _Rules.push_back(rule);
                }

                // If no nested rules (e.g. @font-face), store as single rule
                if (nested.getRuleCount() == 0 && !blockContent.empty()) {
                    CSSRule rule(atSelector);
                    rule.getDeclaration().parse(blockContent);
                    _Rules.push_back(rule);
                }
            } else if (semiPos != std::string::npos) {
                // Simple @-rule ending with semicolon (e.g. @import, @charset)
                std::string atRule = trim(input.substr(atStart, semiPos - atStart));
                CSSRule rule(atRule);
                _Rules.push_back(rule);
                pos = semiPos + 1;
            } else {
                // Malformed @-rule, skip
                ++pos;
            }
            continue;
        }

        // Standard rule: find selector then { declarations }
        size_t braceOpen = input.find('{', pos);
        if (braceOpen == std::string::npos) break;

        std::string selector = trim(input.substr(pos, braceOpen - pos));
        pos = braceOpen + 1;

        // Find matching closing brace
        int depth = 1;
        size_t declStart = pos;
        while (pos < len && depth > 0) {
            if (pos + 1 < len && input[pos] == '/' && input[pos + 1] == '*') {
                _skipComment(input, pos);
                continue;
            }
            if (input[pos] == '{') ++depth;
            else if (input[pos] == '}') --depth;
            if (depth > 0) ++pos;
        }

        std::string declarations = input.substr(declStart, pos - declStart);
        pos = (pos < len) ? pos + 1 : pos;

        if (!selector.empty()) {
            CSSRule rule(selector);
            rule.getDeclaration().parse(declarations);
            _Rules.push_back(rule);
        }
    }
}

void libhtmlpp::CSSStyleSheet::addRule(const CSSRule &rule) {
    _Rules.push_back(rule);
}

void libhtmlpp::CSSStyleSheet::removeRule(size_t index) {
    if (index < _Rules.size()) {
        _Rules.erase(_Rules.begin() + static_cast<std::ptrdiff_t>(index));
    }
}

const libhtmlpp::CSSRule* libhtmlpp::CSSStyleSheet::getRule(size_t index) const {
    if (index < _Rules.size()) return &_Rules[index];
    return nullptr;
}

size_t libhtmlpp::CSSStyleSheet::getRuleCount() const {
    return _Rules.size();
}

const std::vector<libhtmlpp::CSSRule>& libhtmlpp::CSSStyleSheet::getRules() const {
    return _Rules;
}

std::string libhtmlpp::CSSStyleSheet::serialize(bool formatted) const {
    std::string result;
    for (size_t i = 0; i < _Rules.size(); ++i) {
        result += _Rules[i].serialize(formatted);
        if (formatted) result += "\n";
        if (i + 1 < _Rules.size() && formatted) result += "\n";
    }
    return result;
}

void libhtmlpp::CSSStyleSheet::clear() {
    _Rules.clear();
}

libhtmlpp::CSSDeclaration libhtmlpp::CSSStyleSheet::parseInlineStyle(const std::string &style) {
    CSSDeclaration decl;
    decl.parse(style);
    return decl;
}
 
+131 −0
Original line number Diff line number Diff line
/*******************************************************************************
Copyright (c) 2021, Jan Koester jan.koester@gmx.net
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the <organization> nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/

#pragma once

#include <string>
#include <vector>
#include <memory>

namespace libhtmlpp {

    class CSSProperty {
    public:
        CSSProperty();
        CSSProperty(const std::string &name, const std::string &value);
        CSSProperty(const CSSProperty &prop);
        ~CSSProperty();

        CSSProperty& operator=(const CSSProperty &prop);

        const std::string& getName() const;
        void                setName(const std::string &name);

        const std::string& getValue() const;
        void                setValue(const std::string &value);

    private:
        std::string _Name;
        std::string _Value;
    };

    class CSSDeclaration {
    public:
        CSSDeclaration();
        CSSDeclaration(const CSSDeclaration &decl);
        ~CSSDeclaration();

        CSSDeclaration& operator=(const CSSDeclaration &decl);

        void addProperty(const std::string &name, const std::string &value);
        void removeProperty(const std::string &name);

        const CSSProperty* getProperty(const std::string &name) const;

        const std::vector<CSSProperty>& getProperties() const;

        std::string serialize() const;

        void parse(const std::string &input);
        void clear();

    private:
        std::vector<CSSProperty> _Properties;
    };

    class CSSRule {
    public:
        CSSRule();
        CSSRule(const std::string &selector);
        CSSRule(const CSSRule &rule);
        ~CSSRule();

        CSSRule& operator=(const CSSRule &rule);

        const std::string& getSelector() const;
        void                setSelector(const std::string &selector);

        CSSDeclaration&       getDeclaration();
        const CSSDeclaration& getDeclaration() const;

        std::string serialize(bool formatted = false) const;

    private:
        std::string     _Selector;
        CSSDeclaration  _Declaration;
    };

    class CSSStyleSheet {
    public:
        CSSStyleSheet();
        CSSStyleSheet(const CSSStyleSheet &sheet);
        ~CSSStyleSheet();

        CSSStyleSheet& operator=(const CSSStyleSheet &sheet);

        void parse(const std::string &input);

        void addRule(const CSSRule &rule);
        void removeRule(size_t index);

        const CSSRule* getRule(size_t index) const;
        size_t         getRuleCount() const;

        const std::vector<CSSRule>& getRules() const;

        std::string serialize(bool formatted = false) const;
        void clear();

        static CSSDeclaration parseInlineStyle(const std::string &style);

    private:
        void _skipWhitespace(const std::string &input, size_t &pos) const;
        void _skipComment(const std::string &input, size_t &pos) const;
        std::vector<CSSRule> _Rules;
    };

}
 
+187 −8

File changed.

Preview size limit exceeded, changes collapsed.