1#include "jinja/string.h"
  2#include "jinja/value.h"
  3
  4#include <algorithm>
  5#include <functional>
  6#include <optional>
  7#include <sstream>
  8#include <string>
  9#include <vector>
 10
 11namespace jinja {
 12
 13//
 14// string_part
 15//
 16
 17bool string_part::is_uppercase() const {
 18    for (char c : val) {
 19        if (std::islower(static_cast<unsigned char>(c))) {
 20            return false;
 21        }
 22    }
 23    return true;
 24}
 25
 26bool string_part::is_lowercase() const {
 27    for (char c : val) {
 28        if (std::isupper(static_cast<unsigned char>(c))) {
 29            return false;
 30        }
 31    }
 32    return true;
 33}
 34
 35//
 36// string
 37//
 38
 39void string::mark_input() {
 40    for (auto & part : parts) {
 41        part.is_input = true;
 42    }
 43}
 44
 45std::string string::str() const {
 46    if (parts.size() == 1) {
 47        return parts[0].val;
 48    }
 49    std::ostringstream oss;
 50    for (const auto & part : parts) {
 51        oss << part.val;
 52    }
 53    return oss.str();
 54}
 55
 56size_t string::length() const {
 57    size_t len = 0;
 58    for (const auto & part : parts) {
 59        len += part.val.length();
 60    }
 61    return len;
 62}
 63
 64void string::hash_update(hasher & hash) const noexcept {
 65    for (const auto & part : parts) {
 66        hash.update(part.val.data(), part.val.length());
 67    }
 68}
 69
 70bool string::all_parts_are_input() const {
 71    for (const auto & part : parts) {
 72        if (!part.is_input) {
 73            return false;
 74        }
 75    }
 76    return true;
 77}
 78
 79bool string::is_uppercase() const {
 80    for (const auto & part : parts) {
 81        if (!part.is_uppercase()) {
 82            return false;
 83        }
 84    }
 85    return true;
 86}
 87
 88bool string::is_lowercase() const {
 89    for (const auto & part : parts) {
 90        if (!part.is_lowercase()) {
 91            return false;
 92        }
 93    }
 94    return true;
 95}
 96
 97// mark this string as input if other has ALL parts as input
 98void string::mark_input_based_on(const string & other) {
 99    if (other.all_parts_are_input()) {
100        for (auto & part : parts) {
101            part.is_input = true;
102        }
103    }
104}
105
106string string::append(const string & other) {
107    for (const auto & part : other.parts) {
108        parts.push_back(part);
109    }
110    return *this;
111}
112
113// in-place transformation
114
115using transform_fn = std::function<std::string(const std::string&)>;
116static string apply_transform(string & self, const transform_fn & fn) {
117    for (auto & part : self.parts) {
118        part.val = fn(part.val);
119    }
120    return self;
121}
122
123string string::uppercase() {
124    return apply_transform(*this, [](const std::string & s) {
125        std::string res = s;
126        std::transform(res.begin(), res.end(), res.begin(), ::toupper);
127        return res;
128    });
129}
130string string::lowercase() {
131    return apply_transform(*this, [](const std::string & s) {
132        std::string res = s;
133        std::transform(res.begin(), res.end(), res.begin(), ::tolower);
134        return res;
135    });
136}
137string string::capitalize() {
138    return apply_transform(*this, [](const std::string & s) {
139        if (s.empty()) return s;
140        std::string res = s;
141        res[0] = ::toupper(static_cast<unsigned char>(res[0]));
142        std::transform(res.begin() + 1, res.end(), res.begin() + 1, ::tolower);
143        return res;
144    });
145}
146string string::titlecase() {
147    return apply_transform(*this, [](const std::string & s) {
148        std::string res = s;
149        bool capitalize_next = true;
150        for (char &c : res) {
151            if (isspace(static_cast<unsigned char>(c))) {
152                capitalize_next = true;
153            } else if (capitalize_next) {
154                c = ::toupper(static_cast<unsigned char>(c));
155                capitalize_next = false;
156            } else {
157                c = ::tolower(static_cast<unsigned char>(c));
158            }
159        }
160        return res;
161    });
162}
163string string::strip(bool left, bool right, std::optional<const std::string_view> chars) {
164    static auto strip_part = [](const std::string & s, bool left, bool right, std::optional<const std::string_view> chars) -> std::string {
165        size_t start = 0;
166        size_t end = s.length();
167        auto match_char = [&chars](unsigned char c) -> bool {
168            return chars ? (*chars).find(c) != std::string::npos : isspace(c);
169        };
170        if (left) {
171            while (start < end && match_char(static_cast<unsigned char>(s[start]))) {
172                ++start;
173            }
174        }
175        if (right) {
176            while (end > start && match_char(static_cast<unsigned char>(s[end - 1]))) {
177                --end;
178            }
179        }
180        return s.substr(start, end - start);
181    };
182    if (parts.empty()) {
183        return *this;
184    }
185    if (left) {
186        for (size_t i = 0; i < parts.size(); ++i) {
187            parts[i].val = strip_part(parts[i].val, true, false, chars);
188            if (parts[i].val.empty()) {
189                // remove empty part
190                parts.erase(parts.begin() + i);
191                --i;
192                continue;
193            } else {
194                break;
195            }
196        }
197    }
198    if (right) {
199        for (size_t i = parts.size(); i-- > 0;) {
200            parts[i].val = strip_part(parts[i].val, false, true, chars);
201            if (parts[i].val.empty()) {
202                // remove empty part
203                parts.erase(parts.begin() + i);
204                continue;
205            } else {
206                break;
207            }
208        }
209    }
210    return *this;
211}
212
213} // namespace jinja