Skip to content

Commit 53d72c1

Browse files
committed
Add string_joiner_t class and helper function
There are several places where we need to join several elements into a string and it gets tedious to write them correctly. Here is a new class string_joiner_t that helps with that job.
1 parent a0ae47c commit 53d72c1

File tree

5 files changed

+181
-21
lines changed

5 files changed

+181
-21
lines changed

src/flex-table.cpp

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -202,18 +202,15 @@ std::string flex_table_t::build_sql_column_list() const
202202
{
203203
assert(!m_columns.empty());
204204

205-
std::string result;
205+
util::string_joiner_t joiner{',', '"'};
206+
206207
for (auto const &column : m_columns) {
207208
if (!column.create_only()) {
208-
result += '"';
209-
result += column.name();
210-
result += '"';
211-
result += ',';
209+
joiner.add(column.name());
212210
}
213211
}
214-
result.resize(result.size() - 1);
215212

216-
return result;
213+
return joiner();
217214
}
218215

219216
std::string flex_table_t::build_sql_create_id_index() const

src/table.cpp

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -155,28 +155,29 @@ void table_t::prepare()
155155

156156
void table_t::generate_copy_column_list()
157157
{
158-
m_target->rows = "osm_id,";
159-
//first with the regular columns
158+
util::string_joiner_t joiner{',', '"'};
159+
160+
joiner.add("osm_id");
161+
162+
// first with the regular columns
160163
for (auto const &column : m_columns) {
161-
m_target->rows += '"';
162-
m_target->rows += column.name;
163-
m_target->rows += "\",";
164+
joiner.add(column.name);
164165
}
165166

166-
//then with the hstore columns
167+
// then with the hstore columns
167168
for (auto const &hcolumn : m_hstore_columns) {
168-
m_target->rows += '"';
169-
m_target->rows += hcolumn;
170-
m_target->rows += "\",";
169+
joiner.add(hcolumn);
171170
}
172171

173-
//add tags column and geom column
172+
// add tags column
174173
if (m_hstore_mode != hstore_column::none) {
175-
m_target->rows += "tags,way";
176-
//or just the geom column
177-
} else {
178-
m_target->rows += "way";
174+
joiner.add("tags");
179175
}
176+
177+
// add geom column
178+
joiner.add("way");
179+
180+
m_target->rows = joiner();
180181
}
181182

182183
void table_t::stop(bool updateable, bool enable_hstore_index,

src/util.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,49 @@ std::string get_password()
8383
return password;
8484
}
8585

86+
string_joiner_t::string_joiner_t(char delim, char quote, char before,
87+
char after)
88+
: m_delim(delim), m_quote(quote), m_before(before), m_after(after)
89+
{
90+
if (m_before) {
91+
m_result += m_before;
92+
}
93+
}
94+
95+
void string_joiner_t::add(std::string const &item)
96+
{
97+
if (m_quote) {
98+
m_result += m_quote;
99+
m_result += item;
100+
m_result += m_quote;
101+
} else {
102+
m_result += item;
103+
}
104+
m_result += m_delim;
105+
}
106+
107+
std::string string_joiner_t::operator()()
108+
{
109+
if (m_result.size() == 1 && m_before) {
110+
m_result.clear();
111+
} else if (m_result.size() > 1) {
112+
if (m_after) {
113+
m_result.back() = m_after;
114+
} else {
115+
m_result.resize(m_result.size() - 1);
116+
}
117+
}
118+
return std::move(m_result);
119+
}
120+
121+
std::string join(std::vector<std::string> const &items, char delim, char quote,
122+
char before, char after)
123+
{
124+
string_joiner_t joiner{delim, quote, before, after};
125+
for (auto const &item : items) {
126+
joiner.add(item);
127+
}
128+
return joiner();
129+
}
130+
86131
} // namespace util

src/util.hpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <cstdlib>
2222
#include <ctime>
2323
#include <string>
24+
#include <vector>
2425

2526
namespace util {
2627

@@ -158,6 +159,63 @@ auto find_by_name(CONTAINER &container, std::string const &name)
158159
return &*it;
159160
}
160161

162+
/**
163+
* Class to help with joining strings.
164+
*
165+
* After construction call add() as many times as you want. Call the
166+
* call operator to get the result, after which the class is unusable.
167+
*
168+
* Writes out a string like '("A","B","C")' with "delim" set to the comma,
169+
* the double quotes as "quote" character and parentheses as "before" and
170+
* "after" characters. If there are no items (i.e. add() is never called),
171+
* the result is always empty, so the "before" and "after" characters are
172+
* not added in this case.
173+
*/
174+
class string_joiner_t
175+
{
176+
public:
177+
/**
178+
* Constructor
179+
* \param delim Char to output between items.
180+
* \param quote Char to output before and after items (use '\0' for none).
181+
* \param before Char to output before everything else (use '\0' for none).
182+
* \param after Char to output after everything else (use '\0' for none).
183+
*/
184+
explicit string_joiner_t(char delim, char quote = '\0', char before = '\0',
185+
char after = '\0');
186+
187+
/**
188+
* Add one item to be joined. The quote character is added before and
189+
* after the item, but the item itself is added as-is without escaping
190+
* the quote character or anything like that.
191+
*/
192+
void add(std::string const &item);
193+
194+
/// Return result (as value!)
195+
std::string operator()();
196+
197+
private:
198+
std::string m_result;
199+
char m_delim;
200+
char m_quote;
201+
char m_before;
202+
char m_after;
203+
};
204+
205+
/**
206+
* Join all strings in the input vector. This is a convenient wrapper around
207+
* the string_joiner_t class.
208+
*
209+
* \param items The input strings.
210+
* \param delim Character to output between items.
211+
* \param quote Character to output before and after items (use '\0' for none).
212+
* \param before Character to output before everything else (use '\0' for none).
213+
* \param after Character to output after everything else (use '\0' for none).
214+
* \returns Result string.
215+
*/
216+
std::string join(std::vector<std::string> const &items, char delim,
217+
char quote = '\0', char before = '\0', char after = '\0');
218+
161219
} // namespace util
162220

163221
#endif // OSM2PGSQL_UTIL_HPP

tests/test-util.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,62 @@ TEST_CASE("find_by_name()", "[NoDB]")
112112
REQUIRE(util::find_by_name(t, "baz") == &t[2]);
113113
REQUIRE(util::find_by_name(t, "nothing") == nullptr);
114114
}
115+
116+
TEST_CASE("Use string_joiner_t with delim only without items", "[NoDB]")
117+
{
118+
util::string_joiner_t joiner{','};
119+
REQUIRE(joiner().empty());
120+
}
121+
122+
TEST_CASE("Use string_joiner_t with all params without items", "[NoDB]")
123+
{
124+
util::string_joiner_t joiner{',', '"', '(', ')'};
125+
REQUIRE(joiner().empty());
126+
}
127+
128+
TEST_CASE("Use string_joiner_t without quote char", "[NoDB]")
129+
{
130+
util::string_joiner_t joiner{',', '\0', '(', ')'};
131+
joiner.add("foo");
132+
joiner.add("bar");
133+
REQUIRE(joiner() == "(foo,bar)");
134+
}
135+
136+
TEST_CASE("string_joiner_t without before/after", "[NoDB]")
137+
{
138+
util::string_joiner_t joiner{','};
139+
joiner.add("xxx");
140+
joiner.add("yyy");
141+
REQUIRE(joiner() == "xxx,yyy");
142+
}
143+
144+
TEST_CASE("string_joiner_t with single single-char item", "[NoDB]")
145+
{
146+
util::string_joiner_t joiner{','};
147+
joiner.add("x");
148+
REQUIRE(joiner() == "x");
149+
}
150+
151+
TEST_CASE("string_joiner_t with single single-char item and wrapper", "[NoDB]")
152+
{
153+
util::string_joiner_t joiner{',', '\0', '(', ')'};
154+
joiner.add("x");
155+
REQUIRE(joiner() == "(x)");
156+
}
157+
158+
TEST_CASE("join strings", "[NoDB]")
159+
{
160+
std::vector<std::string> const t{"abc", "def", "", "ghi"};
161+
162+
REQUIRE(util::join(t, ',') == "abc,def,,ghi");
163+
REQUIRE(util::join(t, '-', '#', '[', ']') == "[#abc#-#def#-##-#ghi#]");
164+
REQUIRE(util::join(t, '-', '#', '[', ']') == "[#abc#-#def#-##-#ghi#]");
165+
}
166+
167+
TEST_CASE("join strings with empty list", "[NoDB]")
168+
{
169+
std::vector<std::string> const t{};
170+
171+
REQUIRE(util::join(t, ',').empty());
172+
REQUIRE(util::join(t, '-', '#', '[', ']').empty());
173+
}

0 commit comments

Comments
 (0)