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