diff -r e753a7f967c8 -r ae8775e0bc7a src/INIWriter.h --- a/src/INIWriter.h Fri Dec 11 12:34:42 2020 +0100 +++ b/src/INIWriter.h Sat Dec 12 00:01:57 2020 +0100 @@ -22,6 +22,10 @@ #include #include +#include "uri.h" +#include "Dialect.h" +#include "EscapingProcessor.h" + namespace relpipe { namespace out { namespace ini { @@ -35,34 +39,68 @@ std::string commentSeparatorForSections = " ; "; std::string commentSeparatorForEntries = " ; "; std::string commentSeparatorStandalone = "; "; - + bool hasContent = false; - enum class TokenType { - SectionName, - SectionTag, - SectionComment, - EntryKey, - EntrySubKey, - EntryValue, - EntryComment, - StandaloneComment, + /** + * TODO: use a common method + */ + bool parseBoolean(const relpipe::common::type::StringX& value) { + if (value == L"true") return true; + else if (value == L"false") return false; + else throw relpipe::reader::RelpipeReaderException(L"Unable to parse boolean value: " + value + L" (expecting true or false)"); + } + + std::string escape(const relpipe::common::type::StringX& value, EscapingProcessor::TextType type) { + relpipe::common::type::StringX result = value; + EscapingProcessor::QuotingType quotingType = EscapingProcessor::QuotingType::None; + for (ConfiguredEscapingProcessor p : escapingProcessors) if (p.enbaled) result = p.processor->escape(result, type, quotingType); + return convertor.to_bytes(result); + } + + class ConfiguredEscapingProcessor { + public: + std::shared_ptr processor; + const relpipe::common::type::StringX uri; + bool enbaled; + + ConfiguredEscapingProcessor(std::shared_ptr processor, const relpipe::common::type::StringX uri, bool enbaled) : processor(processor), uri(uri), enbaled(enbaled) { + } + }; - std::string escape(TokenType type, relpipe::common::type::StringX value) { - std::wstringstream result; + std::vector escapingProcessors; - for (wchar_t ch : value) { - if (ch == L'\\') result << "\\\\"; - else if (ch == L'\n') result << L"\\n"; - else if (ch == L'\r') result << L"\\r"; - else if (ch == L'\t') result << L"\\t"; - else if (ch == L'"') result << "\\\""; - else result.put(ch); + bool setEscaping(const relpipe::common::type::StringX& uri, const relpipe::common::type::StringX& value) { + for (ConfiguredEscapingProcessor& p : escapingProcessors) { + if (p.uri == uri) { + p.enbaled = parseBoolean(value); + return true; + } + } + return false; + } + + class ConfiguredDialect { + public: + std::shared_ptr dialect; + const relpipe::common::type::StringX uri; + + ConfiguredDialect(std::shared_ptr dialect, const relpipe::common::type::StringX uri) : dialect(dialect), uri(uri) { } - // TODO: modular escaping (like unescaping in relpipe-in-ini) - return convertor.to_bytes(result.str()); + }; + + std::vector dialects; + + void setDialect(const relpipe::common::type::StringX& uri) { + for (ConfiguredDialect& d : dialects) { + if (d.uri == uri) { + d.dialect->apply(*this); + return; + } + } + throw relpipe::reader::RelpipeReaderException(L"Unsupported INI dialect: " + uri); } public: @@ -99,18 +137,27 @@ }; void setOption(relpipe::common::type::StringX uri, relpipe::common::type::StringX value) { - // TODO: setOption() - if (uri == L"dialect"); - else if (uri == L"comment-separator-for-sections") commentSeparatorForSections = convertor.to_bytes(value); - else if (uri == L"comment-separator-for-entries") commentSeparatorForEntries = convertor.to_bytes(value); - else if (uri == L"comment-separator-standalone") commentSeparatorStandalone = convertor.to_bytes(value); - else if (uri == L"key-value-separator") keyValueSeparator = convertor.to_bytes(value); - else if (uri == L"escape-backspace"); - else if (uri == L"escape-basic"); - else if (uri == L"escape-java-properties"); + // TODO: setOption(): escaping, quotes, allow-sections + if (uri == option::Dialect) setDialect(value); + else if (uri == option::CommentSeparatorForSections) commentSeparatorForSections = convertor.to_bytes(value); + else if (uri == option::CommentSeparatorForEntries) commentSeparatorForEntries = convertor.to_bytes(value); + else if (uri == option::CommentSeparatorStandalone) commentSeparatorStandalone = convertor.to_bytes(value); + else if (uri == option::KeyValueSeparator) keyValueSeparator = convertor.to_bytes(value); + else if (uri == option::AllowSections); + else if (uri == option::Quotes); + else if (setEscaping(uri, value)); else throw relpipe::reader::RelpipeReaderException(L"Unsupported writer option: " + uri); } + void addDialect(std::shared_ptr dialect, const relpipe::common::type::StringX uri, bool enabledByDefault) { + dialects.push_back({dialect, uri}); + if (enabledByDefault) dialect->apply(*this); + } + + void addEscapingProcessor(std::shared_ptr processor, const relpipe::common::type::StringX uri, bool enabledByDefault) { + escapingProcessors.push_back({processor, uri, enabledByDefault}); + } + void startDocument() { } @@ -120,9 +167,9 @@ void startSection(const SectionStartEvent& event) { if (hasContent) output << std::endl; - output << "[" << escape(TokenType::SectionName, event.name) << "]"; - if (event.tag.size()) output << "[" << escape(TokenType::SectionTag, event.tag) << "]"; - if (event.comment.size()) output << commentSeparatorForSections << escape(TokenType::SectionComment, event.comment); + output << "[" << escape(event.name, EscapingProcessor::TextType::SectionName) << "]"; + if (event.tag.size()) output << "[" << escape(event.tag, EscapingProcessor::TextType::SectionTag) << "]"; + if (event.comment.size()) output << commentSeparatorForSections << escape(event.comment, EscapingProcessor::TextType::SectionComment); output << std::endl; hasContent = true; } @@ -132,16 +179,16 @@ } void entry(const EntryEvent& event) { - output << escape(TokenType::EntryKey, event.key); - if (event.subKey.size()) output << "[" << escape(TokenType::EntrySubKey, event.subKey) << "]"; - output << keyValueSeparator << escape(TokenType::EntryValue, event.value); - if (event.comment.size()) output << commentSeparatorForEntries << escape(TokenType::EntryComment, event.comment); + output << escape(event.key, EscapingProcessor::TextType::EntryKey); + if (event.subKey.size()) output << "[" << escape(event.subKey, EscapingProcessor::TextType::EntrySubKey) << "]"; + output << keyValueSeparator << escape(event.value, EscapingProcessor::TextType::EntryValue); + if (event.comment.size()) output << commentSeparatorForEntries << escape(event.comment, EscapingProcessor::TextType::EntryComment); output << std::endl; hasContent = true; } void comment(const CommentEvent& event) { - output << commentSeparatorStandalone << escape(TokenType::StandaloneComment, event.comment); + output << commentSeparatorStandalone << escape(event.comment, EscapingProcessor::TextType::StandaloneComment); output << std::endl; hasContent = true; }