#include <config.h>
#include <util/csv_file.h>
+#include <util/str.h>
#include <algorithm>
#include <iostream>
void
CSVRow::writeAtEscaped(const size_t at, const std::string& value) {
- writeAt(at, escapeCharacters(value, separator_));
+ writeAt(at, escapeCharacters(value, separator_.at(0)));
}
void
const std::string CSVRow::escape_tag("&#x");
std::string
-CSVRow::escapeCharacters(const std::string& orig_str, const std::string& characters) {
- size_t char_pos = 0;
- size_t prev_pos = 0;
-
- // We add the first character of the escape tag to the list of
- // characters to escape. This ensures input which happens to
- // be valid escape sequences will be escaped.
- std::string escape_chars(characters + escape_tag[0]);
-
- // Check for a first occurrence. If none, just return a
- // copy of the original.
- char_pos = orig_str.find_first_of(escape_chars, prev_pos);
- if (char_pos == std::string::npos) {
- return(orig_str);
- }
-
- std::stringstream ss;
- while (char_pos < orig_str.size()) {
- // Copy everything upto the character to escape.
- ss << orig_str.substr(prev_pos, char_pos - prev_pos);
-
- // Copy the escape tag followed by the hex digits of the character.
- ss << escape_tag << std::hex << std::setw(2)
- << static_cast<uint16_t>(orig_str[char_pos]);
-
- ++char_pos;
- prev_pos = char_pos;
-
- // Find the next character to escape.
- char_pos = orig_str.find_first_of(escape_chars, prev_pos);
-
- // If no more, copy the remainder of the string.
- if (char_pos == std::string::npos) {
- ss << orig_str.substr(prev_pos, char_pos - prev_pos);
- break;
- }
-
+CSVRow::escapeCharacters(const std::string& orig_str, const char separator) {
+ auto escape_it = [](char c, char s, char e) -> bool {
+ return ((c < 0x20) || (c > 0x7e) || c == s || c == e);
};
- // Return the escaped string.
- return(ss.str());
+ // Count the number of needed escapes.
+ size_t escapes = 0;
+ for (char c : orig_str) {
+ if (escape_it(c, separator, escape_tag[0])) {
+ ++escapes;
+ }
+ }
+
+ if (escapes == 0) {
+ // Nothing to escape, return the original.
+ return (orig_str);
+ }
+
+ // Make the result large enough to avoid reallocations.
+ std::string esc_str;
+ esc_str.reserve(orig_str.size() + escapes * (escape_tag.size() + 1));
+ // Iterate over the original string, escaped chars that need it.
+ for (char c : orig_str) {
+ if (escape_it(c, separator, escape_tag[0])) {
+ esc_str.append(escape_tag);
+ esc_str.append(str::byteToHex(c));
+ } else {
+ esc_str.push_back(c);
+ }
+ }
+
+ return (esc_str);
}
std::string
if (dig_pos <= escaped_str.size() - 2) {
for (int i = 0; i < 2; ++i) {
uint8_t digit = escaped_str[dig_pos];
-
- if (digit >= 'a' && digit <= 'f') {
+ if (digit >= '0' && digit <= '9') {
+ digit -= '0';
+ }
+ else if (digit >= 'a' && digit <= 'f') {
digit = digit - 'a' + 10;
} else if (digit >= 'A' && digit <= 'F') {
digit = digit - 'A' + 10;
- } else if (digit >= '0' && digit <= '9') {
- digit -= '0';
} else {
converted = false;
break;
return(ss.str());
}
-
} // end of isc::util namespace
} // end of isc namespace
#include <config.h>
#include <util/csv_file.h>
+#include <util/str.h>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <fstream>
// This test exercises escaping and unescaping of characters.
TEST(CSVRowTest, escapeUnescape) {
- std::string orig(",FO^O\\,B?,AR,");
-
- // We'll escape commas, question marks, and carets.
- std::string escaped = CSVRow::escapeCharacters(orig, ",?^");
- EXPECT_EQ ("ˏO^O\\ˋ?ˊR,", escaped);
-
- // Now make sure we can unescape it correctly.
- std::string unescaped = CSVRow::unescapeCharacters(escaped);
- EXPECT_EQ (orig, unescaped);
-
- // Make sure that an incident occurrence of just the escape tag
- // is left intact.
- orig = ("noscape");
- escaped = CSVRow::escapeCharacters(orig, ",");
- unescaped = CSVRow::unescapeCharacters(orig);
- EXPECT_EQ (orig, unescaped);
-
- // Make sure that an incidental occurrence of a valid
- // escape tag sequence left intact.
- orig = ("noˎscape");
- escaped = CSVRow::escapeCharacters(orig, ",");
- unescaped = CSVRow::unescapeCharacters(escaped);
- EXPECT_EQ (orig, unescaped);
+ // Make a string of all characters.
+ std::stringstream ss;
+ for (uint16_t i = 1; i < 256; ++i) {
+ ss << static_cast<uint8_t>(i);
+ }
+
+ std::string expected_escaped =
+ "	

"
+ ""
+ " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMN"
+ "OPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚"
+ "ƒ„…†‡ˆ‰Š‹ŒŽ"
+ "‘’“”•–—˜™š›œž"
+ "Ÿ ¡¢£¤¥¦§¨©ª«¬"
+ "­®¯°±²³´µ¶·¸¹º"
+ "»¼½¾¿ÀÁÂÃÄÅÆÇÈ"
+ "ÉÊËÌÍÎÏÐÑÒÓÔÕÖ"
+ "רÙÚÛÜÝÞßàáâãä"
+ "åæçèéêëìíîïðñò"
+ "óôõö÷øùúûüýþÿ";
+
+ // Create the escaped string. Use comma for a delimiter.
+ auto org = ss.str();
+ auto escaped = CSVRow::escapeCharacters(org, ',');
+
+ ASSERT_EQ(escaped, expected_escaped);
+
+ // Unescape it and make sure we get the original back.
+ auto unescaped = CSVRow::unescapeCharacters(escaped);
+ EXPECT_EQ(unescaped, org);
+
+ // Make sure an empty input string is fine.
+ std::string blank;
+ auto ret = CSVRow::escapeCharacters(blank, ',');
+ EXPECT_EQ(ret, blank);
}
// This test checks that the single data row is parsed.
// This test checks that the data values can be set for the CSV row.
TEST(CSVRow, writeAt) {
- CSVRow row(4);
+ CSVRow row(5);
row.writeAt(0, 10);
row.writeAt(1, "foo");
row.writeAt(2, "bar");
- row.writeAtEscaped(3, "bar,one,two");
+ row.writeAtEscaped(3, "bar,one,two\nthree");
+ row.writeAt(4, "melon");
EXPECT_EQ("10", row.readAt(0));
EXPECT_EQ("foo", row.readAt(1));
EXPECT_EQ("bar", row.readAt(2));
// Read third column as-is and unescaped
- EXPECT_EQ("bar,one,two", row.readAt(3));
- EXPECT_EQ("bar,one,two", row.readAtEscaped(3));
+ EXPECT_EQ("bar,one,two
three", row.readAt(3));
+ EXPECT_EQ("bar,one,two\nthree", row.readAtEscaped(3));
- EXPECT_THROW(row.writeAt(4, 20), CSVFileError);
- EXPECT_THROW(row.writeAt(4, "foo"), CSVFileError);
+ // Fourth column should be fine even though third column
+ // contains a linefeed.
+ EXPECT_EQ("melon", row.readAt(4));
+
+ // Can't write past row limit.
+ EXPECT_THROW(row.writeAt(5, 20), CSVFileError);
+ EXPECT_THROW(row.writeAt(5, "foo"), CSVFileError);
}
// Checks whether writeAt() and append() can be mixed together.
}
}
+TEST(CSVRow, speedCheck) {
+ std::string org = "abce,1234,,xyz,99,&88,";
+
+ for (int i = 0; i < 1000000; ++i) {
+ auto escaped = CSVRow::escapeCharacters(org, ',');
+ auto unescaped = CSVRow::unescapeCharacters(escaped);
+ ASSERT_EQ(unescaped, org);
+ }
+}
+
} // end of anonymous namespace