33 |
37 |
34 std::string keyValueSeparator = " = "; |
38 std::string keyValueSeparator = " = "; |
35 std::string commentSeparatorForSections = " ; "; |
39 std::string commentSeparatorForSections = " ; "; |
36 std::string commentSeparatorForEntries = " ; "; |
40 std::string commentSeparatorForEntries = " ; "; |
37 std::string commentSeparatorStandalone = "; "; |
41 std::string commentSeparatorStandalone = "; "; |
38 |
42 |
39 bool hasContent = false; |
43 bool hasContent = false; |
40 |
44 |
41 enum class TokenType { |
45 /** |
42 SectionName, |
46 * TODO: use a common method |
43 SectionTag, |
47 */ |
44 SectionComment, |
48 bool parseBoolean(const relpipe::common::type::StringX& value) { |
45 EntryKey, |
49 if (value == L"true") return true; |
46 EntrySubKey, |
50 else if (value == L"false") return false; |
47 EntryValue, |
51 else throw relpipe::reader::RelpipeReaderException(L"Unable to parse boolean value: " + value + L" (expecting true or false)"); |
48 EntryComment, |
52 } |
49 StandaloneComment, |
53 |
50 }; |
54 std::string escape(const relpipe::common::type::StringX& value, EscapingProcessor::TextType type) { |
51 |
55 relpipe::common::type::StringX result = value; |
52 std::string escape(TokenType type, relpipe::common::type::StringX value) { |
56 EscapingProcessor::QuotingType quotingType = EscapingProcessor::QuotingType::None; |
53 std::wstringstream result; |
57 for (ConfiguredEscapingProcessor p : escapingProcessors) if (p.enbaled) result = p.processor->escape(result, type, quotingType); |
54 |
58 return convertor.to_bytes(result); |
55 for (wchar_t ch : value) { |
59 } |
56 if (ch == L'\\') result << "\\\\"; |
60 |
57 else if (ch == L'\n') result << L"\\n"; |
61 class ConfiguredEscapingProcessor { |
58 else if (ch == L'\r') result << L"\\r"; |
62 public: |
59 else if (ch == L'\t') result << L"\\t"; |
63 std::shared_ptr<EscapingProcessor> processor; |
60 else if (ch == L'"') result << "\\\""; |
64 const relpipe::common::type::StringX uri; |
61 else result.put(ch); |
65 bool enbaled; |
62 } |
66 |
63 |
67 ConfiguredEscapingProcessor(std::shared_ptr<EscapingProcessor> processor, const relpipe::common::type::StringX uri, bool enbaled) : processor(processor), uri(uri), enbaled(enbaled) { |
64 // TODO: modular escaping (like unescaping in relpipe-in-ini) |
68 } |
65 return convertor.to_bytes(result.str()); |
69 |
|
70 }; |
|
71 |
|
72 std::vector<ConfiguredEscapingProcessor> escapingProcessors; |
|
73 |
|
74 bool setEscaping(const relpipe::common::type::StringX& uri, const relpipe::common::type::StringX& value) { |
|
75 for (ConfiguredEscapingProcessor& p : escapingProcessors) { |
|
76 if (p.uri == uri) { |
|
77 p.enbaled = parseBoolean(value); |
|
78 return true; |
|
79 } |
|
80 } |
|
81 return false; |
|
82 } |
|
83 |
|
84 class ConfiguredDialect { |
|
85 public: |
|
86 std::shared_ptr<Dialect> dialect; |
|
87 const relpipe::common::type::StringX uri; |
|
88 |
|
89 ConfiguredDialect(std::shared_ptr<Dialect> dialect, const relpipe::common::type::StringX uri) : dialect(dialect), uri(uri) { |
|
90 } |
|
91 |
|
92 }; |
|
93 |
|
94 std::vector<ConfiguredDialect> dialects; |
|
95 |
|
96 void setDialect(const relpipe::common::type::StringX& uri) { |
|
97 for (ConfiguredDialect& d : dialects) { |
|
98 if (d.uri == uri) { |
|
99 d.dialect->apply(*this); |
|
100 return; |
|
101 } |
|
102 } |
|
103 throw relpipe::reader::RelpipeReaderException(L"Unsupported INI dialect: " + uri); |
66 } |
104 } |
67 |
105 |
68 public: |
106 public: |
69 |
107 |
70 class SectionStartEvent { |
108 class SectionStartEvent { |
97 |
135 |
98 virtual ~INIWriter() { |
136 virtual ~INIWriter() { |
99 }; |
137 }; |
100 |
138 |
101 void setOption(relpipe::common::type::StringX uri, relpipe::common::type::StringX value) { |
139 void setOption(relpipe::common::type::StringX uri, relpipe::common::type::StringX value) { |
102 // TODO: setOption() |
140 // TODO: setOption(): escaping, quotes, allow-sections |
103 if (uri == L"dialect"); |
141 if (uri == option::Dialect) setDialect(value); |
104 else if (uri == L"comment-separator-for-sections") commentSeparatorForSections = convertor.to_bytes(value); |
142 else if (uri == option::CommentSeparatorForSections) commentSeparatorForSections = convertor.to_bytes(value); |
105 else if (uri == L"comment-separator-for-entries") commentSeparatorForEntries = convertor.to_bytes(value); |
143 else if (uri == option::CommentSeparatorForEntries) commentSeparatorForEntries = convertor.to_bytes(value); |
106 else if (uri == L"comment-separator-standalone") commentSeparatorStandalone = convertor.to_bytes(value); |
144 else if (uri == option::CommentSeparatorStandalone) commentSeparatorStandalone = convertor.to_bytes(value); |
107 else if (uri == L"key-value-separator") keyValueSeparator = convertor.to_bytes(value); |
145 else if (uri == option::KeyValueSeparator) keyValueSeparator = convertor.to_bytes(value); |
108 else if (uri == L"escape-backspace"); |
146 else if (uri == option::AllowSections); |
109 else if (uri == L"escape-basic"); |
147 else if (uri == option::Quotes); |
110 else if (uri == L"escape-java-properties"); |
148 else if (setEscaping(uri, value)); |
111 else throw relpipe::reader::RelpipeReaderException(L"Unsupported writer option: " + uri); |
149 else throw relpipe::reader::RelpipeReaderException(L"Unsupported writer option: " + uri); |
|
150 } |
|
151 |
|
152 void addDialect(std::shared_ptr<Dialect> dialect, const relpipe::common::type::StringX uri, bool enabledByDefault) { |
|
153 dialects.push_back({dialect, uri}); |
|
154 if (enabledByDefault) dialect->apply(*this); |
|
155 } |
|
156 |
|
157 void addEscapingProcessor(std::shared_ptr<EscapingProcessor> processor, const relpipe::common::type::StringX uri, bool enabledByDefault) { |
|
158 escapingProcessors.push_back({processor, uri, enabledByDefault}); |
112 } |
159 } |
113 |
160 |
114 void startDocument() { |
161 void startDocument() { |
115 } |
162 } |
116 |
163 |
118 output.flush(); |
165 output.flush(); |
119 } |
166 } |
120 |
167 |
121 void startSection(const SectionStartEvent& event) { |
168 void startSection(const SectionStartEvent& event) { |
122 if (hasContent) output << std::endl; |
169 if (hasContent) output << std::endl; |
123 output << "[" << escape(TokenType::SectionName, event.name) << "]"; |
170 output << "[" << escape(event.name, EscapingProcessor::TextType::SectionName) << "]"; |
124 if (event.tag.size()) output << "[" << escape(TokenType::SectionTag, event.tag) << "]"; |
171 if (event.tag.size()) output << "[" << escape(event.tag, EscapingProcessor::TextType::SectionTag) << "]"; |
125 if (event.comment.size()) output << commentSeparatorForSections << escape(TokenType::SectionComment, event.comment); |
172 if (event.comment.size()) output << commentSeparatorForSections << escape(event.comment, EscapingProcessor::TextType::SectionComment); |
126 output << std::endl; |
173 output << std::endl; |
127 hasContent = true; |
174 hasContent = true; |
128 } |
175 } |
129 |
176 |
130 void endSection() { |
177 void endSection() { |
131 output.flush(); |
178 output.flush(); |
132 } |
179 } |
133 |
180 |
134 void entry(const EntryEvent& event) { |
181 void entry(const EntryEvent& event) { |
135 output << escape(TokenType::EntryKey, event.key); |
182 output << escape(event.key, EscapingProcessor::TextType::EntryKey); |
136 if (event.subKey.size()) output << "[" << escape(TokenType::EntrySubKey, event.subKey) << "]"; |
183 if (event.subKey.size()) output << "[" << escape(event.subKey, EscapingProcessor::TextType::EntrySubKey) << "]"; |
137 output << keyValueSeparator << escape(TokenType::EntryValue, event.value); |
184 output << keyValueSeparator << escape(event.value, EscapingProcessor::TextType::EntryValue); |
138 if (event.comment.size()) output << commentSeparatorForEntries << escape(TokenType::EntryComment, event.comment); |
185 if (event.comment.size()) output << commentSeparatorForEntries << escape(event.comment, EscapingProcessor::TextType::EntryComment); |
139 output << std::endl; |
186 output << std::endl; |
140 hasContent = true; |
187 hasContent = true; |
141 } |
188 } |
142 |
189 |
143 void comment(const CommentEvent& event) { |
190 void comment(const CommentEvent& event) { |
144 output << commentSeparatorStandalone << escape(TokenType::StandaloneComment, event.comment); |
191 output << commentSeparatorStandalone << escape(event.comment, EscapingProcessor::TextType::StandaloneComment); |
145 output << std::endl; |
192 output << std::endl; |
146 hasContent = true; |
193 hasContent = true; |
147 } |
194 } |
148 |
195 |
149 void whitespace(const WhitespaceEvent& event) { |
196 void whitespace(const WhitespaceEvent& event) { |