App/Toponaming: Add base64 encoding to XML

This commit is contained in:
Chris Hennes
2023-10-01 16:15:47 -05:00
parent ddf2a3723b
commit 33abb7b5b1
9 changed files with 634 additions and 136 deletions

View File

@@ -1,128 +1,162 @@
/*
base64.cpp and base64.h
/*
base64.cpp and base64.h
Copyright (C) 2004-2008 René Nyffenegger
Copyright (C) 2004-2008 René Nyffenegger
This source code is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
This source code is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this source code must not be misrepresented; you must not
claim that you wrote the original source code. If you use this source code
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
1. The origin of this source code must not be misrepresented; you must not
claim that you wrote the original source code. If you use this source code
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original source code.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original source code.
3. This notice may not be removed or altered from any source distribution.
3. This notice may not be removed or altered from any source distribution.
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
Copyright (C) 2019 Zheng Lei (realthunder.dev@gmail.com)
* Added support of in-line transformation using boost iostream for memory
efficiency
*/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <string>
#include <iostream>
#include <string>
#endif
#include "Base64.h"
// clazy:excludeall=non-pod-global-static
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static inline bool is_base64(unsigned char c) {
return (isalnum(c) || (c == '+') || (c == '/'));
const signed char* Base::base64_decode_table()
{
static const signed char _table[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -1, -2, -1, -1, // 0-15
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16-31
-2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // 32-47
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, // 48-63
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64-79
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 80-95
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96-111
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, // 112-127
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128-143
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 144-159
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 160-175
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 176-191
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 192-207
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 208-223
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 224-239
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 240-255
};
return _table;
}
std::string Base::base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
std::string ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
std::size_t Base::base64_encode(char* out, void const* in, std::size_t in_len)
{
char* ret = out;
unsigned char const* bytes_to_encode = reinterpret_cast<unsigned char const*>(in);
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
while (in_len--) {
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
while (in_len--) {
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for(i = 0; (i <4) ; i++)
ret += base64_chars[char_array_4[i]];
i = 0;
for (i = 0; (i < 4); i++) {
*ret++ = base64_chars[char_array_4[i]];
}
i = 0;
}
}
}
if (i)
{
for(j = i; j < 3; j++)
char_array_3[j] = '\0';
if (i) {
for (j = i; j < 3; j++) {
char_array_3[j] = '\0';
}
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; (j < i + 1); j++)
ret += base64_chars[char_array_4[j]];
for (j = 0; (j < i + 1); j++) {
*ret++ = base64_chars[char_array_4[j]];
}
while((i++ < 3))
ret += '=';
}
return ret;
}
std::string Base::base64_decode(std::string const& encoded_string) {
int in_len = encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;
while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
char_array_4[i++] = encoded_string[in_]; in_++;
if (i ==4) {
for (i = 0; i <4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++)
ret += char_array_3[i];
i = 0;
while ((i++ < 3)) {
*ret++ = '=';
}
}
}
if (i) {
for (j = i; j <4; j++)
char_array_4[j] = 0;
for (j = 0; j <4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
}
return ret;
return ret - out;
}
std::pair<std::size_t, std::size_t>
Base::base64_decode(void* _out, char const* in, std::size_t in_len)
{
unsigned char* out = reinterpret_cast<unsigned char*>(_out);
unsigned char* ret = out;
char const* input = in;
int i = 0;
int j = 0;
unsigned char char_array_4[4], char_array_3[3];
static const signed char* table = base64_decode_table();
while (in_len-- && *in != '=') {
const signed char v = table[(int)(*in++)];
if (v < 0) {
break;
}
char_array_4[i++] = (unsigned char)v;
if (i == 4) {
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++) {
*ret++ = char_array_3[i];
}
i = 0;
}
}
if (i) {
for (j = i; j < 4; j++) {
char_array_4[j] = 0;
}
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++) {
*ret++ = char_array_3[j];
}
}
return std::make_pair((std::size_t)(ret - out), (std::size_t)(in - input));
}

View File

@@ -1,42 +1,439 @@
/*
base64.cpp and base64.h
/*
base64.cpp and base64.h
Copyright (C) 2004-2008 René Nyffenegger
Copyright (C) 2004-2008 René Nyffenegger
This source code is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
This source code is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this source code must not be misrepresented; you must not
claim that you wrote the original source code. If you use this source code
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
1. The origin of this source code must not be misrepresented; you must not
claim that you wrote the original source code. If you use this source code
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original source code.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original source code.
3. This notice may not be removed or altered from any source distribution.
3. This notice may not be removed or altered from any source distribution.
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
Copyright (C) 2019 Zheng Lei (realthunder.dev@gmail.com)
* Added support of in-line transformation using boost iostream for memory
efficiency
*/
#ifndef BASE_BASE64_H
#define BASE_BASE64_H
#include <FCGlobal.h>
#include <boost/iostreams/concepts.hpp>
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/operations.hpp>
#include <memory>
#include <string>
#include <vector>
namespace Base
{
std::string BaseExport base64_encode(unsigned char const* , unsigned int len);
std::string BaseExport base64_decode(std::string const& s);
enum class Base64ErrorHandling
{
throws,
silent
};
/// Returns the max bytes of a encoded base64 string
inline std::size_t base64_encode_size(std::size_t len)
{
return 4 * ((len + 2) / 3);
}
#endif
/// Returns the max bytes of a decoded base64 binary string
inline std::size_t base64_decode_size(std::size_t len)
{
return len / 4 * 3;
}
/** Encode input binary with base64
* @param out: output buffer with minimum size of base64_encode(len)
* appending new data.
* @param in: input binary data
* @param len: input length
* @return The character count written to output.
*/
BaseExport std::size_t base64_encode(char* out, void const* in, std::size_t len);
/** Return the internal base64 decoding table
*
* The table maps from any 8-bit character to the decoded binary bits.
* Valid base64 characters are mapped to the corresponding 6-bit binary
* data. White space (space, tab, vtab, CR and LF) characters are mapped
* to -2. Other invalid characters are mapped to -1.
*/
BaseExport const signed char* base64_decode_table();
/** Decode the input base64 string into binary data
* @param out: output buffer with minimum size of base64_encode(len)
* appending new data.
* @param in: input binary data
* @param len: input length
* @return Return a pair of output size and input read size. Compare the
* read size to input size to check for error.
*/
BaseExport std::pair<std::size_t, std::size_t>
base64_decode(void* out, char const*, std::size_t len);
/** Encode input binary into base64 string
* @param out: output string. Note that the string is not cleared before
* adding new content.
* @param in: input binary data
* @param len: input length
*/
inline void base64_encode(std::string& out, void const* in, std::size_t len)
{
std::size_t size = out.size();
out.resize(size + base64_encode_size(len));
len = base64_encode(&out[size], in, len);
out.resize(size + len);
}
/** Encode input binary into base64 string
* @param in: input binary data
* @param len: input length
* @return Return the base64 string.
*/
inline std::string base64_encode(void const* in, std::size_t len)
{
std::string out;
base64_encode(out, in, len);
return out;
}
/** Decode base64 string into binary data
* @param out: output binary data. Note that the data is not cleared before
* adding new content.
* @param in: input base64 string
* @param len: input length
* @return Return the processed input length. Compare this with the
* argument \c len to check for error.
*/
template<typename T>
inline std::size_t base64_decode(T& out, char const* in, std::size_t len)
{
std::size_t size = out.size();
out.resize(size + base64_decode_size(len));
std::pair<std::size_t, std::size_t> res = base64_decode(&out[size], in, len);
out.resize(size + res.first);
return res.second;
}
/** Decode base64 string into binary data
* @param out: output binary data. Note that the data is not cleared before
* adding new content.
* @param s: input base64 string
* @return Return the processed input length. Compare this with the
* argument \c len to check for error.
*/
template<typename T>
inline std::size_t base64_decode(T& out, std::string const& s)
{
return base64_decode(out, s.c_str(), s.size());
}
/** Decode base64 string into binary data
* @param out adding new content.
* @param s: input base64 string
* @return Return the decoded binary data.
*/
inline std::string base64_decode(std::string const& s)
{
std::string out;
base64_decode(out, s.c_str(), s.size());
return out;
}
namespace bio = boost::iostreams;
/** A base64 encoder that can be used as a boost iostream filter
*
* @sa See create_base64_encoder() for example usage
*/
struct base64_encoder
{
typedef char char_type;
struct category: bio::multichar_output_filter_tag,
bio::closable_tag,
bio::optimally_buffered_tag
{
};
/** Constructor
* @param line_size: line size for the output base64 string, 0 to
* disable segmentation.
*/
base64_encoder(std::size_t line_size)
: line_size(line_size)
{}
std::streamsize optimal_buffer_size() const
{
return base64_decode_size(line_size);
}
template<typename Device>
void close(Device& dev)
{
if (pending_size) {
base64_encode(buffer, pending, pending_size);
}
if (buffer.size()) {
bio::write(dev, buffer.c_str(), buffer.size());
if (line_size) {
bio::put(dev, '\n');
}
buffer.clear();
}
else if (pos && line_size) {
bio::put(dev, '\n');
}
}
template<typename Device>
std::streamsize write(Device& dev, const char_type* s, std::streamsize n)
{
std::streamsize res = n;
if (pending_size) {
while (n && pending_size < 3) {
pending[pending_size++] = *s++;
--n;
}
if (pending_size != 3) {
return res;
}
base64_encode(buffer, pending, 3);
}
pending_size = n % 3;
n = n / 3 * 3;
base64_encode(buffer, s, n);
s += n;
for (unsigned i = 0; i < pending_size; ++i) {
pending[i] = s[i];
}
const char* buf = buffer.c_str();
const char* end = buf + buffer.size();
if (line_size && buffer.size() >= line_size - pos) {
bio::write(dev, buf, line_size - pos);
bio::put(dev, '\n');
buf += line_size - pos;
pos = 0;
for (; end - buf >= (int)line_size; buf += line_size) {
bio::write(dev, buf, line_size);
bio::put(dev, '\n');
}
}
pos += end - buf;
bio::write(dev, buf, end - buf);
buffer.clear();
return n;
}
std::size_t line_size;
std::size_t pos = 0;
std::size_t pending_size = 0;
unsigned char pending[3];
std::string buffer;
};
/** A base64 decoder that can be used as a boost iostream filter
*
* @sa See create_base64_decoder() for example usage
*/
struct base64_decoder
{
typedef char char_type;
struct category: bio::multichar_input_filter_tag, bio::optimally_buffered_tag
{
};
/** Constructor
* @param line_size: line size of the encoded base64 string. This is
* used just as a suggestion for better buffering.
* @param silent: whether to throw on invalid non white space character.
*/
base64_decoder(std::size_t line_size, Base64ErrorHandling errHandling)
: line_size(line_size)
, errHandling(errHandling)
{}
std::streamsize optimal_buffer_size() const
{
return base64_encode_size(line_size != 0U ? line_size : 1024);
}
template<typename Device>
std::streamsize read(Device& dev, char_type* s, std::streamsize n)
{
static const signed char* table = base64_decode_table();
if (!n) {
return 0;
}
std::streamsize count = 0;
for (;;) {
while (pending_out < out_count) {
*s++ = char_array_3[pending_out++];
++count;
if (--n == 0) {
return count;
}
}
if (eof) {
return count ? count : -1;
}
for (;;) {
int d = bio::get(dev);
if (d < 0) {
eof = true;
if (pending_in <= 1) {
if (pending_in == 1 && errHandling == Base64ErrorHandling::throws) {
throw BOOST_IOSTREAMS_FAILURE("Unexpected ending of base64 string");
}
return count ? count : -1;
}
out_count = pending_in - 1;
pending_in = 4;
}
else {
signed char c = table[d];
if (c < 0) {
if (c == -2 || errHandling == Base64ErrorHandling::silent) {
continue;
}
throw BOOST_IOSTREAMS_FAILURE("Invalid character in base64 string");
}
char_array_4[pending_in++] = (char)c;
}
if (pending_in == 4) {
pending_out = pending_in = 0;
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] =
((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
break;
}
}
}
}
std::size_t line_size;
std::uint8_t pending_in = 0;
char char_array_4[4];
std::uint8_t pending_out = 3;
std::uint8_t out_count = 3;
char char_array_3[3];
Base64ErrorHandling errHandling;
bool eof = false;
};
/** Create an output stream that transforms the input binary data to base64 strings
*
* @param out: the downstream output stream that will be fed with base64 string
* @param line_size: line size of the base64 string. Zero to disable segmenting.
*
* @return A unique pointer to an output stream that can transforms the
* input binary data to base64 strings.
*/
inline std::unique_ptr<std::ostream> create_base64_encoder(std::ostream& out,
std::size_t line_size = 80)
{
std::unique_ptr<std::ostream> res(new bio::filtering_ostream);
auto* f = static_cast<bio::filtering_ostream*>(res.get());
f->push(base64_encoder(line_size));
f->push(out);
return res;
}
/** Create an output stream that stores the input binary data to file as base64 strings
*
* @param filename: the output file path
* @param line_size: line size of the base64 string. Zero to disable segmenting.
*
* @return A unique pointer to an output stream that can transforms the
* input binary data to base64 strings.
*/
inline std::unique_ptr<std::ostream> create_base64_encoder(const std::string& filepath,
std::size_t line_size = 80)
{
std::unique_ptr<std::ostream> res(new bio::filtering_ostream);
bio::filtering_ostream* f = static_cast<bio::filtering_ostream*>(res.get());
f->push(base64_encoder(line_size));
f->push(bio::file_sink(filepath));
return res;
}
/** Create an input stream that can transform base64 into binary
*
* @param in: input upstream.
* @param line_size: line size of the encoded base64 string. This is
* used just as a suggestion for better buffering.
* @param silent: whether to throw on invalid non white space character.
*
* @return A unique pointer to an input stream that read from the given
* upstream and transform the read base64 strings into binary data.
*/
inline std::unique_ptr<std::istream>
create_base64_decoder(std::istream& in,
std::size_t line_size = 80,
Base64ErrorHandling errHandling = Base64ErrorHandling::silent)
{
std::unique_ptr<std::istream> res(new bio::filtering_istream);
bio::filtering_istream* f = static_cast<bio::filtering_istream*>(res.get());
f->push(base64_decoder(line_size, errHandling));
f->push(in);
return res;
}
/** Create an input stream that can transform base64 into binary
*
* @param filepath: input file.
* @param ending: optional ending character. If non zero, the filter
* will signal EOF when encounter this character.
* @param putback: if true and the filter read the ending character
* it will put it back into upstream
* @param line_size: line size of the encoded base64 string. This is
* used just as a suggestion for better buffering.
* @param silent: whether to throw on invalid non white space character.
*
* @return A unique pointer to an input stream that read from the given
* file and transform the read base64 strings into binary data.
*/
inline std::unique_ptr<std::istream>
create_base64_decoder(const std::string& filepath,
std::size_t line_size = 80,
Base64ErrorHandling errHandling = Base64ErrorHandling::silent)
{
std::unique_ptr<std::istream> res(new bio::filtering_istream);
auto* f = static_cast<bio::filtering_istream*>(res.get());
f->push(base64_decoder(line_size, errHandling));
f->push(bio::file_source(filepath));
return res;
}
} // namespace Base
#endif

View File

@@ -34,6 +34,15 @@
namespace Base
{
/// When reading and writing a character stream, the incoming data can be dumped into the stream
/// unaltered (if it contains only data that is valid in the current XML character set), or it can
/// be Base64-encoded. This enum is used by Reader and Writer to distinguish the two cases.
enum class CharStreamFormat {
Raw,
Base64Encoded
};
static constexpr int base64LineWidth {80};
/** File name unification
* This class handles everything related to file names
* the file names are internal generally UTF-8 encoded on

View File

@@ -283,8 +283,17 @@ void Base::XMLReader::readEndElement(const char* ElementName, int level)
|| (level>=0 && level!=Level))));
}
void Base::XMLReader::readCharacters()
void Base::XMLReader::readCharacters(const char* filename, CharStreamFormat format)
{
Base::FileInfo fi(filename);
Base::ofstream to(fi, std::ios::out | std::ios::binary | std::ios::trunc);
if (!to) {
throw Base::FileException("XMLReader::readCharacters() Could not open file!");
}
beginCharStream(format) >> to.rdbuf();
to.close();
endCharStream();
}
std::streamsize Base::XMLReader::read(char_type* s, std::streamsize n)
@@ -336,7 +345,7 @@ std::istream& Base::XMLReader::charStream()
return *CharStream;
}
std::istream& Base::XMLReader::beginCharStream()
std::istream& Base::XMLReader::beginCharStream(CharStreamFormat format)
{
if (CharStream) {
throw Base::XMLParseException("recursive character stream");
@@ -362,6 +371,9 @@ std::istream& Base::XMLReader::beginCharStream()
CharStream = std::make_unique<boost::iostreams::filtering_istream>();
auto* filteringStream = dynamic_cast<boost::iostreams::filtering_istream*>(CharStream.get());
if(format == CharStreamFormat::Base64Encoded) {
filteringStream->push(base64_decoder(Base::base64LineWidth, Base64ErrorHandling::silent));
}
filteringStream->push(boost::ref(*this));
return *CharStream;
}

View File

@@ -175,7 +175,7 @@ public:
*/
void readEndElement(const char* ElementName=nullptr, int level=-1);
/// read until characters are found
void readCharacters();
void readCharacters(const char* filename, CharStreamFormat format = CharStreamFormat::Raw);
/** Obtain an input stream for reading characters
*
@@ -183,7 +183,7 @@ public:
* auto destroyed when you call with readElement() or readEndElement(), or
* you can end it explicitly with endCharStream().
*/
std::istream &beginCharStream();
std::istream &beginCharStream(CharStreamFormat format = CharStreamFormat::Raw);
/// Manually end the current character stream
void endCharStream();
/// Obtain the current character stream
@@ -200,7 +200,7 @@ public:
unsigned int getAttributeCount() const;
/// check if the read element has a special attribute
bool hasAttribute(const char* AttrName) const;
/// return the named attribute as an interer (does type checking)
/// return the named attribute as an integer (does type checking)
long getAttributeAsInteger(const char* AttrName) const;
unsigned long getAttributeAsUnsigned(const char* AttrName) const;
/// return the named attribute as a double floating point (does type checking)

View File

@@ -83,18 +83,22 @@ Writer::Writer()
Writer::~Writer() = default;
std::ostream& Writer::beginCharStream()
std::ostream& Writer::beginCharStream(CharStreamFormat format)
{
if (CharStream) {
throw Base::RuntimeError("Writer::beginCharStream(): invalid state");
}
Stream() << "<![CDATA[";
CharStream = std::make_unique<boost::iostreams::filtering_ostream>();
auto* filteredStream = dynamic_cast<boost::iostreams::filtering_ostream*>(CharStream.get());
filteredStream->push(cdata_filter());
filteredStream->push(Stream());
*filteredStream << std::setprecision(std::numeric_limits<double>::digits10 + 1);
charStreamFormat = format;
if(format == CharStreamFormat::Base64Encoded) {
CharStream = create_base64_encoder(Stream(), Base::base64LineWidth);
} else {
Stream() << "<![CDATA[";
CharStream = std::make_unique<boost::iostreams::filtering_ostream>();
auto* filteredStream = dynamic_cast<boost::iostreams::filtering_ostream*>(CharStream.get());
filteredStream->push(cdata_filter());
filteredStream->push(Stream());
*filteredStream << std::setprecision(std::numeric_limits<double>::digits10 + 1);
}
return *CharStream;
}
@@ -102,7 +106,9 @@ std::ostream& Writer::endCharStream()
{
if (CharStream) {
CharStream.reset();
Stream() << "]]>";
if (charStreamFormat == CharStreamFormat::Raw) {
Stream() << "]]>";
}
}
return Stream();
}

View File

@@ -123,11 +123,17 @@ public:
* the current XML encoding, and will be enclosed inside
* CDATA section. The stream will scan the input and
* properly escape any CDATA ending inside.
*
* @param format: If Base64Encoded, the input will be base64 encoded before storing.
* If Raw, the input is assumed to be valid character with
* the current XML encoding, and will be enclosed inside
* CDATA section. The stream will scan the input and
* properly escape any CDATA ending inside.
* @return Returns an output stream.
*
* You must call endCharStream() to end the current character stream.
*/
std::ostream &beginCharStream();
std::ostream& beginCharStream(CharStreamFormat format = CharStreamFormat::Raw);
/** End the current character output stream
* @return Returns the normal writer stream for convenience
*/
@@ -161,6 +167,7 @@ public:
private:
std::unique_ptr<std::ostream> CharStream;
CharStreamFormat charStreamFormat;
};

View File

@@ -265,3 +265,21 @@ TEST_F(ReaderTest, readNextStartEndElement)
EXPECT_STREQ(Reader()->localName(), "node2");
EXPECT_STREQ(Reader()->getAttribute("attr"), "2");
}
TEST_F(ReaderTest, charStreamBase64Encoded)
{
// Arrange
static constexpr size_t bufferSize {100};
std::array<char, bufferSize> buffer {};
givenDataAsXMLStream("<data>RnJlZUNBRCByb2NrcyEg8J+qqPCfqqjwn6qo\n</data>");
Reader()->readElement("data");
Reader()->beginCharStream(Base::CharStreamFormat::Base64Encoded);
// Act
Reader()->charStream().getline(buffer.data(), bufferSize);
Reader()->endCharStream();
// Assert
// Conversion done using https://www.base64encode.org for testing purposes
EXPECT_EQ(std::string("FreeCAD rocks! 🪨🪨🪨"), std::string(buffer.data()));
}

View File

@@ -114,3 +114,18 @@ TEST_F(WriterTest, charStream)
// Assert
EXPECT_EQ(&streamA, &streamB);
}
TEST_F(WriterTest, charStreamBase64Encoded)
{
// Arrange
auto& stream {_writer.beginCharStream(Base::CharStreamFormat::Base64Encoded)};
std::string data {"FreeCAD rocks! 🪨🪨🪨"};
// Act
_writer.charStream() << data;
_writer.endCharStream();
// Assert
// Conversion done using https://www.base64encode.org for testing purposes
EXPECT_EQ(std::string("RnJlZUNBRCByb2NrcyEg8J+qqPCfqqjwn6qo\n"), _writer.getString());
}