Audacity 3.2.0
XMLWriter.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 XMLWriter.cpp
6
7 Leland Lucius
8
9*******************************************************************//****************************************************************//****************************************************************//*******************************************************************/
26
27#include "XMLWriter.h"
28
29#include <wx/defs.h>
30#include <wx/ffile.h>
31
32#include <cstring>
33
34#include "ToChars.h"
35
37
38//table for xml encoding compatibility with expat decoding
39//see wxWidgets-2.8.12/src/expat/lib/xmltok_impl.h
40//and wxWidgets-2.8.12/src/expat/lib/asciitab.h
41static int charXMLCompatiblity[] =
42 {
43
44/* 0x00 */ 0, 0, 0, 0,
45/* 0x04 */ 0, 0, 0, 0,
46/* 0x08 */ 0, 1, 1, 0,
47/* 0x0C */ 0, 1, 0, 0,
48/* 0x10 */ 0, 0, 0, 0,
49/* 0x14 */ 0, 0, 0, 0,
50/* 0x18 */ 0, 0, 0, 0,
51/* 0x1C */ 0, 0, 0, 0,
52 };
53
54// These are used by XMLEsc to handle surrogate pairs and filter invalid characters outside the ASCII range.
55#define MIN_HIGH_SURROGATE static_cast<wxUChar>(0xD800)
56#define MAX_HIGH_SURROGATE static_cast<wxUChar>(0xDBFF)
57#define MIN_LOW_SURROGATE static_cast<wxUChar>(0xDC00)
58#define MAX_LOW_SURROGATE static_cast<wxUChar>(0xDFFF)
59
60// Unicode defines other noncharacters, but only these two are invalid in XML.
61#define NONCHARACTER_FFFE static_cast<wxUChar>(0xFFFE)
62#define NONCHARACTER_FFFF static_cast<wxUChar>(0xFFFF)
63
64
69{
70 mDepth = 0;
71 mInTag = false;
72 mHasKids.push_back(false);
73}
74
76{
77}
78
79void XMLWriter::StartTag(const wxString &name)
80// may throw
81{
82 int i;
83
84 if (mInTag) {
85 Write(wxT(">\n"));
86 mInTag = false;
87 }
88
89 for (i = 0; i < mDepth; i++) {
90 Write(wxT("\t"));
91 }
92
93 Write(wxString::Format(wxT("<%s"), name));
94
95 mTagstack.insert(mTagstack.begin(), name);
96 mHasKids[0] = true;
97 mHasKids.insert(mHasKids.begin(), false);
98 mDepth++;
99 mInTag = true;
100}
101
102void XMLWriter::EndTag(const wxString &name)
103// may throw
104{
105 int i;
106
107 if (mTagstack.size() > 0) {
108 if (mTagstack[0] == name) {
109 if (mHasKids[1]) { // There will always be at least 2 at this point
110 if (mInTag) {
111 Write(wxT("/>\n"));
112 }
113 else {
114 for (i = 0; i < mDepth - 1; i++) {
115 Write(wxT("\t"));
116 }
117 Write(wxString::Format(wxT("</%s>\n"), name));
118 }
119 }
120 else {
121 Write(wxT(">\n"));
122 }
123 mTagstack.erase( mTagstack.begin() );
124 mHasKids.erase(mHasKids.begin());
125 }
126 }
127
128 mDepth--;
129 mInTag = false;
130}
131
132void XMLWriter::WriteAttr(const wxString &name, const wxString &value)
133// may throw from Write()
134{
135 Write(wxString::Format(wxT(" %s=\"%s\""),
136 name,
137 XMLEsc(value)));
138}
139
140void XMLWriter::WriteAttr(const wxString &name, const wxChar *value)
141// may throw from Write()
142{
143 WriteAttr(name, wxString(value));
144}
145
146void XMLWriter::WriteAttr(const wxString &name, int value)
147// may throw from Write()
148{
149 Write(wxString::Format(wxT(" %s=\"%d\""),
150 name,
151 value));
152}
153
154void XMLWriter::WriteAttr(const wxString &name, bool value)
155// may throw from Write()
156{
157 Write(wxString::Format(wxT(" %s=\"%d\""),
158 name,
159 value));
160}
161
162void XMLWriter::WriteAttr(const wxString &name, long value)
163// may throw from Write()
164{
165 Write(wxString::Format(wxT(" %s=\"%ld\""),
166 name,
167 value));
168}
169
170void XMLWriter::WriteAttr(const wxString &name, long long value)
171// may throw from Write()
172{
173 Write(wxString::Format(wxT(" %s=\"%lld\""),
174 name,
175 value));
176}
177
178void XMLWriter::WriteAttr(const wxString &name, size_t value)
179// may throw from Write()
180{
181 Write(wxString::Format(wxT(" %s=\"%lld\""),
182 name,
183 (long long) value));
184}
185
186void XMLWriter::WriteAttr(const wxString &name, float value, int digits)
187// may throw from Write()
188{
189 Write(wxString::Format(wxT(" %s=\"%s\""),
190 name,
191 Internat::ToString(value, digits)));
192}
193
194void XMLWriter::WriteAttr(const wxString &name, double value, int digits)
195// may throw from Write()
196{
197 Write(wxString::Format(wxT(" %s=\"%s\""),
198 name,
199 Internat::ToString(value, digits)));
200}
201
202void XMLWriter::WriteData(const wxString &value)
203// may throw from Write()
204{
205 int i;
206
207 for (i = 0; i < mDepth; i++) {
208 Write(wxT("\t"));
209 }
210
211 Write(XMLEsc(value));
212}
213
214void XMLWriter::WriteSubTree(const wxString &value)
215// may throw from Write()
216{
217 if (mInTag) {
218 Write(wxT(">\n"));
219 mInTag = false;
220 mHasKids[0] = true;
221 }
222
223 Write(value);
224}
225
226// See http://www.w3.org/TR/REC-xml for reference
227wxString XMLWriter::XMLEsc(const wxString & s)
228{
229 wxString result;
230 int len = s.length();
231
232 for(int i=0; i<len; i++) {
233 wxUChar c = s.GetChar(i);
234
235 switch (c) {
236 case wxT('\''):
237 result += wxT("&apos;");
238 break;
239
240 case wxT('"'):
241 result += wxT("&quot;");
242 break;
243
244 case wxT('&'):
245 result += wxT("&amp;");
246 break;
247
248 case wxT('<'):
249 result += wxT("&lt;");
250 break;
251
252 case wxT('>'):
253 result += wxT("&gt;");
254 break;
255
256 default:
257 if (sizeof(c) == 2 && c >= MIN_HIGH_SURROGATE && c <= MAX_HIGH_SURROGATE && i < len - 1) {
258 // If wxUChar is 2 bytes, then supplementary characters (those greater than U+FFFF) are represented
259 // with a high surrogate (U+D800..U+DBFF) followed by a low surrogate (U+DC00..U+DFFF).
260 // Handle those here.
261 wxUChar c2 = s.GetChar(++i);
262 if (c2 >= MIN_LOW_SURROGATE && c2 <= MAX_LOW_SURROGATE) {
263 // Surrogate pair found; simply add it to the output string.
264 result += c;
265 result += c2;
266 }
267 else {
268 // That high surrogate isn't paired, so ignore it.
269 i--;
270 }
271 }
272 else if (!wxIsprint(c)) {
273 //ignore several characters such ase eot (0x04) and stx (0x02) because it makes expat parser bail
274 //see xmltok.c in expat checkCharRefNumber() to see how expat bails on these chars.
275 //also see wxWidgets-2.8.12/src/expat/lib/asciitab.h to see which characters are nonxml compatible
276 //post decode (we can still encode '&' and '<' with this table, but it prevents us from encoding eot)
277 //everything is compatible past ascii 0x20 except for surrogates and the noncharacters U+FFFE and U+FFFF,
278 //so we don't check the compatibility table higher than this.
279 if((c> 0x1F || charXMLCompatiblity[c]!=0) &&
280 (c < MIN_HIGH_SURROGATE || c > MAX_LOW_SURROGATE) &&
282 result += wxString::Format(wxT("&#x%04x;"), c);
283 }
284 else {
285 result += c;
286 }
287 break;
288 }
289 }
290
291 return result;
292}
293
298 const FilePath &outputPath, const TranslatableString &caption, bool keepBackup )
299 : mOutputPath{ outputPath }
300 , mCaption{ caption }
301 , mKeepBackup{ keepBackup }
302// may throw
303{
304 auto tempPath = wxFileName::CreateTempFileName( outputPath );
305 if (!wxFFile::Open(tempPath, wxT("wb")) || !IsOpened())
306 ThrowException( outputPath, mCaption );
307
308 if (mKeepBackup) {
309 int index = 0;
310 wxString backupName;
311
312 do {
313 wxFileName outputFn{ mOutputPath };
314 index++;
316 outputFn.GetPath() + wxFILE_SEP_PATH +
317 outputFn.GetName() + wxT("_bak") +
318 wxString::Format(wxT("%d"), index) + wxT(".") +
319 outputFn.GetExt();
320 } while( ::wxFileExists( mBackupName ) );
321
322 // Open the backup file to be sure we can write it and reserve it
323 // until committing
324 if (! mBackupFile.Open( mBackupName, "wb" ) || ! mBackupFile.IsOpened() )
326 }
327}
328
329
331{
332 // Don't let a destructor throw!
333 GuardedCall( [&] {
334 if (!mCommitted) {
335 auto fileName = GetName();
336 if ( IsOpened() )
338 ::wxRemoveFile( fileName );
339 }
340 } );
341}
342
344// may throw
345{
346 PreCommit();
347 PostCommit();
348}
349
351// may throw
352{
353 while (mTagstack.size()) {
354 EndTag(mTagstack[0]);
355 }
356
358}
359
361// may throw
362{
363 FilePath tempPath = GetName();
364 if (mKeepBackup) {
365 if (! mBackupFile.Close() ||
366 ! wxRenameFile( mOutputPath, mBackupName ) )
368 }
369 else {
370 if ( wxFileName::FileExists( mOutputPath ) &&
371 ! wxRemoveFile( mOutputPath ) )
373 }
374
375 // Now we have vacated the file at the output path and are committed.
376 // But not completely finished with steps of the commit operation.
377 // If this step fails, we haven't lost the successfully written data,
378 // but just failed to put it in the right place.
379 if (! wxRenameFile( tempPath, mOutputPath ) )
380 throw FileException{
382 };
383
384 mCommitted = true;
385}
386
388// may throw
389{
390 // Before closing, we first flush it, because if Flush() fails because of a
391 // "disk full" condition, we can still at least try to close the file.
392 if (!wxFFile::Flush())
393 {
394 wxFFile::Close();
395 ThrowException( GetName(), mCaption );
396 }
397
398 // Note that this should never fail if flushing worked.
399 if (!wxFFile::Close())
400 ThrowException( GetName(), mCaption );
401}
402
403void XMLFileWriter::Write(const wxString &data)
404// may throw
405{
406 if (!wxFFile::Write(data, wxConvUTF8) || Error())
407 {
408 // When writing fails, we try to close the file before throwing the
409 // exception, so it can at least be deleted.
410 wxFFile::Close();
411 ThrowException( GetName(), mCaption );
412 }
413}
414
419{
420 if (initialSize)
421 {
422 reserve(initialSize);
423 }
424}
425
427{
428}
429
430void XMLStringWriter::Write(const wxString &data)
431{
432 Append(data);
433}
434
435void XMLUtf8BufferWriter::StartTag(const std::string_view& name)
436{
437 if (mInTag)
438 Write(">");
439
440 Write("<");
441 Write(name);
442
443 mInTag = true;
444}
445
446void XMLUtf8BufferWriter::EndTag(const std::string_view& name)
447{
448 if (mInTag)
449 {
450 Write("/>");
451 mInTag = false;
452 }
453 else
454 {
455 Write("</");
456 Write(name);
457 Write(">");
458 }
459}
460
461void XMLUtf8BufferWriter::WriteAttr(const std::string_view& name, const Identifier& value)
462{
463 const wxScopedCharBuffer utf8Value = value.GET().utf8_str();
464
465 WriteAttr(name, { utf8Value.data(), utf8Value.length() });
466}
467
469 const std::string_view& name, const std::string_view& value)
470{
471 assert(mInTag);
472
473 Write(" ");
474 Write(name);
475 Write("=\"");
476 WriteEscaped(value);
477 Write("\"");
478}
479
480void XMLUtf8BufferWriter::WriteAttr(const std::string_view& name, int value)
481{
482 WriteAttr(name, static_cast<long long>(value));
483}
484
485void XMLUtf8BufferWriter::WriteAttr(const std::string_view& name, bool value)
486{
487 WriteAttr(name, static_cast<long long>(value));
488}
489
490void XMLUtf8BufferWriter::WriteAttr(const std::string_view& name, long value)
491{
492 // long can be int or long long. Assume the longest!
493 WriteAttr(name, static_cast<long long>(value));
494}
495
497 const std::string_view& name, long long value)
498{
499 // -9223372036854775807 is the worst case
500 constexpr size_t bufferSize = 21;
501 char buffer[bufferSize];
502
503 const auto result = ToChars(buffer, buffer + bufferSize, value);
504
505 if (result.ec != std::errc())
507
508 WriteAttr(name, std::string_view(buffer, result.ptr - buffer));
509}
510
511void XMLUtf8BufferWriter::WriteAttr(const std::string_view& name, size_t value)
512{
513 // Well, that maintains the original behavior
514 WriteAttr(name, static_cast<long long>(value));
515}
516
518 const std::string_view& name, float value, int digits /*= -1*/)
519{
520 constexpr size_t bufferSize = std::numeric_limits<float>::max_digits10 +
521 5 + // No constexpr log2 yet! example - e-308
522 3; // Dot, sign an 0 separator
523
524 char buffer[bufferSize];
525
526 const auto result = ToChars(buffer, buffer + bufferSize, value, digits);
527
528 if (result.ec != std::errc())
530
531 WriteAttr(name, std::string_view(buffer, result.ptr - buffer));
532}
533
535 const std::string_view& name, double value, int digits /*= -1*/)
536{
537 constexpr size_t bufferSize = std::numeric_limits<double>::max_digits10 +
538 5 + // No constexpr log2 yet!
539 3; // Dot, sign an 0 separator
540
541 char buffer[bufferSize];
542
543 const auto result = ToChars(buffer, buffer + bufferSize, value, digits);
544
545 if (result.ec != std::errc())
547
548 WriteAttr(name, std::string_view(buffer, result.ptr - buffer));
549}
550
551void XMLUtf8BufferWriter::WriteData(const std::string_view& value)
552{
553 if (mInTag)
554 {
555 Write(">");
556 mInTag = false;
557 }
558
559 WriteEscaped(value);
560}
561
562void XMLUtf8BufferWriter::WriteSubTree(const std::string_view& value)
563{
564 if (mInTag)
565 {
566 Write(">");
567 mInTag = false;
568 }
569
570 Write(value);
571}
572
573void XMLUtf8BufferWriter::Write(const std::string_view& value)
574{
575 mStream.AppendData(value.data(), value.length());
576}
577
579{
580 return std::move(mStream);
581}
582
583void XMLUtf8BufferWriter::WriteEscaped(const std::string_view& value)
584{
585 for (auto c : value)
586 {
587 switch (c)
588 {
589 case wxT('\''):
590 Write("&apos;");
591 break;
592
593 case wxT('"'):
594 Write("&quot;");
595 break;
596
597 case wxT('&'):
598 Write("&amp;");
599 break;
600
601 case wxT('<'):
602 Write("&lt;");
603 break;
604
605 case wxT('>'):
606 Write("&gt;");
607 break;
608 default:
609 if (static_cast<uint8_t>(c) > 0x1F || charXMLCompatiblity[c] != 0)
611 }
612 }
613}
wxT("CloseDown"))
R GuardedCall(const F1 &body, const F2 &handler=F2::Default(), F3 delayedHandler=DefaultDelayedHandlerAction) noexcept(noexcept(handler(std::declval< AudacityException * >())) &&noexcept(handler(nullptr)) &&noexcept(std::function< void(AudacityException *)>{std::move(delayedHandler)}))
Execute some code on any thread; catch any AudacityException; enqueue error report on the main thread...
const TranslatableString name
Definition: Distortion.cpp:76
MessageBoxException for violation of preconditions or assertions.
#define THROW_INCONSISTENCY_EXCEPTION
Throw InconsistencyException, using C++ preprocessor to identify the source code location.
wxString FilePath
Definition: Project.h:21
STRING_UTILS_API ToCharsResult ToChars(char *buffer, char *last, float value, int digitsAfterDecimalPoint) noexcept
Convert a single precision floating point number to a string, always uses the dot as decimal.
Definition: ToChars.cpp:1225
Define functions to convert numeric types to string representation.
Append(Adapt< My >([](My &table) { return(WaveChannelSubViews::numFactories() > 1) ? std::make_unique< Entry >("MultiView", Entry::CheckItem, OnMultiViewID, XXO("&Multi-view"), POPUP_MENU_FN(OnMultiView), table, [](PopupMenuHandler &handler, wxMenu &menu, int id){ auto &table=static_cast< WaveTrackMenuTable & >(handler);auto &track=table.FindWaveTrack();const auto &view=WaveChannelView::GetFirst(track);menu.Check(id, view.GetMultiView());}) :nullptr;}))
#define MIN_HIGH_SURROGATE
Definition: XMLWriter.cpp:55
#define NONCHARACTER_FFFE
Definition: XMLWriter.cpp:61
#define MAX_LOW_SURROGATE
Definition: XMLWriter.cpp:58
static int charXMLCompatiblity[]
Definition: XMLWriter.cpp:41
#define NONCHARACTER_FFFF
Definition: XMLWriter.cpp:62
#define MAX_HIGH_SURROGATE
Definition: XMLWriter.cpp:56
#define MIN_LOW_SURROGATE
Definition: XMLWriter.cpp:57
Thrown for failure of file or database operations in deeply nested places.
Definition: FileException.h:19
@ Rename
involves two filenames
An explicitly nonlocalized string, not meant for the user to see.
Definition: Identifier.h:22
const wxString & GET() const
Explicit conversion to wxString, meant to be ugly-looking and demanding of a comment why it's correct...
Definition: Identifier.h:66
static wxString ToString(double numberToConvert, int digitsAfterDecimalPoint=-1)
Convert a number to a string, always uses the dot as decimal separator.
Definition: Internat.cpp:126
A low overhead memory stream with O(1) append, low heap fragmentation and a linear memory view.
void AppendData(const void *data, const size_t length)
void AppendByte(char data)
Holds a msgid for the translation catalog; may also bind format arguments.
wxFFile mBackupFile
Definition: XMLWriter.h:131
bool mCommitted
Definition: XMLWriter.h:133
void PostCommit()
Definition: XMLWriter.cpp:360
FilePath mBackupName
Definition: XMLWriter.h:128
const TranslatableString mCaption
Definition: XMLWriter.h:127
void Write(const wxString &data) override
Write to file. Might throw.
Definition: XMLWriter.cpp:403
void PreCommit()
Does the part of Commit that might fail because of exhaustion of space.
Definition: XMLWriter.cpp:350
const bool mKeepBackup
Definition: XMLWriter.h:129
virtual ~XMLFileWriter()
Definition: XMLWriter.cpp:330
void CloseWithoutEndingTags()
Definition: XMLWriter.cpp:387
void ThrowException(const wxFileName &fileName, const TranslatableString &caption)
Definition: XMLWriter.h:116
const FilePath mOutputPath
Definition: XMLWriter.h:126
XMLFileWriter(const FilePath &outputPath, const TranslatableString &caption, bool keepBackup=false)
Definition: XMLWriter.cpp:297
XMLStringWriter(size_t initialSize=0)
Definition: XMLWriter.cpp:418
virtual ~XMLStringWriter()
Definition: XMLWriter.cpp:426
void Write(const wxString &data) override
Definition: XMLWriter.cpp:430
void WriteData(const std::string_view &value)
Definition: XMLWriter.cpp:551
void WriteSubTree(const std::string_view &value)
Definition: XMLWriter.cpp:562
void StartTag(const std::string_view &name)
Definition: XMLWriter.cpp:435
void EndTag(const std::string_view &name)
Definition: XMLWriter.cpp:446
MemoryStream mStream
Definition: XMLWriter.h:182
void WriteAttr(const std::string_view &name, const Identifier &value)
Definition: XMLWriter.cpp:461
void WriteEscaped(const std::string_view &value)
Definition: XMLWriter.cpp:583
MemoryStream ConsumeResult()
Definition: XMLWriter.cpp:578
void Write(const std::string_view &value)
Definition: XMLWriter.cpp:573
virtual void WriteData(const wxString &value)
Definition: XMLWriter.cpp:202
std::vector< int > mHasKids
Definition: XMLWriter.h:68
virtual void StartTag(const wxString &name)
Definition: XMLWriter.cpp:79
static wxString XMLEsc(const wxString &s)
Definition: XMLWriter.cpp:227
void WriteAttr(const wxString &name, const Identifier &value)
Definition: XMLWriter.h:36
virtual void EndTag(const wxString &name)
Definition: XMLWriter.cpp:102
wxArrayString mTagstack
Definition: XMLWriter.h:67
bool mInTag
Definition: XMLWriter.h:65
virtual ~XMLWriter()
Definition: XMLWriter.cpp:75
virtual void Write(const wxString &data)=0
int mDepth
Definition: XMLWriter.h:66
virtual void WriteSubTree(const wxString &value)
Definition: XMLWriter.cpp:214