Audacity  3.0.3
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 #include <wx/intl.h>
32 
33 #include <string.h>
34 
35 //table for xml encoding compatibility with expat decoding
36 //see wxWidgets-2.8.12/src/expat/lib/xmltok_impl.h
37 //and wxWidgets-2.8.12/src/expat/lib/asciitab.h
38 static int charXMLCompatiblity[] =
39  {
40 
41 /* 0x00 */ 0, 0, 0, 0,
42 /* 0x04 */ 0, 0, 0, 0,
43 /* 0x08 */ 0, 1, 1, 0,
44 /* 0x0C */ 0, 1, 0, 0,
45 /* 0x10 */ 0, 0, 0, 0,
46 /* 0x14 */ 0, 0, 0, 0,
47 /* 0x18 */ 0, 0, 0, 0,
48 /* 0x1C */ 0, 0, 0, 0,
49  };
50 
51 // These are used by XMLEsc to handle surrogate pairs and filter invalid characters outside the ASCII range.
52 #define MIN_HIGH_SURROGATE static_cast<wxUChar>(0xD800)
53 #define MAX_HIGH_SURROGATE static_cast<wxUChar>(0xDBFF)
54 #define MIN_LOW_SURROGATE static_cast<wxUChar>(0xDC00)
55 #define MAX_LOW_SURROGATE static_cast<wxUChar>(0xDFFF)
56 
57 // Unicode defines other noncharacters, but only these two are invalid in XML.
58 #define NONCHARACTER_FFFE static_cast<wxUChar>(0xFFFE)
59 #define NONCHARACTER_FFFF static_cast<wxUChar>(0xFFFF)
60 
61 
66 {
67  mDepth = 0;
68  mInTag = false;
69  mHasKids.push_back(false);
70 }
71 
73 {
74 }
75 
76 void XMLWriter::StartTag(const wxString &name)
77 // may throw
78 {
79  int i;
80 
81  if (mInTag) {
82  Write(wxT(">\n"));
83  mInTag = false;
84  }
85 
86  for (i = 0; i < mDepth; i++) {
87  Write(wxT("\t"));
88  }
89 
90  Write(wxString::Format(wxT("<%s"), name));
91 
92  mTagstack.insert(mTagstack.begin(), name);
93  mHasKids[0] = true;
94  mHasKids.insert(mHasKids.begin(), false);
95  mDepth++;
96  mInTag = true;
97 }
98 
99 void XMLWriter::EndTag(const wxString &name)
100 // may throw
101 {
102  int i;
103 
104  if (mTagstack.size() > 0) {
105  if (mTagstack[0] == name) {
106  if (mHasKids[1]) { // There will always be at least 2 at this point
107  if (mInTag) {
108  Write(wxT("/>\n"));
109  }
110  else {
111  for (i = 0; i < mDepth - 1; i++) {
112  Write(wxT("\t"));
113  }
114  Write(wxString::Format(wxT("</%s>\n"), name));
115  }
116  }
117  else {
118  Write(wxT(">\n"));
119  }
120  mTagstack.erase( mTagstack.begin() );
121  mHasKids.erase(mHasKids.begin());
122  }
123  }
124 
125  mDepth--;
126  mInTag = false;
127 }
128 
129 void XMLWriter::WriteAttr(const wxString &name, const wxString &value)
130 // may throw from Write()
131 {
132  Write(wxString::Format(wxT(" %s=\"%s\""),
133  name,
134  XMLEsc(value)));
135 }
136 
137 void XMLWriter::WriteAttr(const wxString &name, const wxChar *value)
138 // may throw from Write()
139 {
140  WriteAttr(name, wxString(value));
141 }
142 
143 void XMLWriter::WriteAttr(const wxString &name, int value)
144 // may throw from Write()
145 {
146  Write(wxString::Format(wxT(" %s=\"%d\""),
147  name,
148  value));
149 }
150 
151 void XMLWriter::WriteAttr(const wxString &name, bool value)
152 // may throw from Write()
153 {
154  Write(wxString::Format(wxT(" %s=\"%d\""),
155  name,
156  value));
157 }
158 
159 void XMLWriter::WriteAttr(const wxString &name, long value)
160 // may throw from Write()
161 {
162  Write(wxString::Format(wxT(" %s=\"%ld\""),
163  name,
164  value));
165 }
166 
167 void XMLWriter::WriteAttr(const wxString &name, long long value)
168 // may throw from Write()
169 {
170  Write(wxString::Format(wxT(" %s=\"%lld\""),
171  name,
172  value));
173 }
174 
175 void XMLWriter::WriteAttr(const wxString &name, size_t value)
176 // may throw from Write()
177 {
178  Write(wxString::Format(wxT(" %s=\"%lld\""),
179  name,
180  (long long) value));
181 }
182 
183 void XMLWriter::WriteAttr(const wxString &name, float value, int digits)
184 // may throw from Write()
185 {
186  Write(wxString::Format(wxT(" %s=\"%s\""),
187  name,
188  Internat::ToString(value, digits)));
189 }
190 
191 void XMLWriter::WriteAttr(const wxString &name, double value, int digits)
192 // may throw from Write()
193 {
194  Write(wxString::Format(wxT(" %s=\"%s\""),
195  name,
196  Internat::ToString(value, digits)));
197 }
198 
199 void XMLWriter::WriteData(const wxString &value)
200 // may throw from Write()
201 {
202  int i;
203 
204  for (i = 0; i < mDepth; i++) {
205  Write(wxT("\t"));
206  }
207 
208  Write(XMLEsc(value));
209 }
210 
211 void XMLWriter::WriteSubTree(const wxString &value)
212 // may throw from Write()
213 {
214  if (mInTag) {
215  Write(wxT(">\n"));
216  mInTag = false;
217  mHasKids[0] = true;
218  }
219 
220  Write(value);
221 }
222 
223 // See http://www.w3.org/TR/REC-xml for reference
224 wxString XMLWriter::XMLEsc(const wxString & s)
225 {
226  wxString result;
227  int len = s.length();
228 
229  for(int i=0; i<len; i++) {
230  wxUChar c = s.GetChar(i);
231 
232  switch (c) {
233  case wxT('\''):
234  result += wxT("&apos;");
235  break;
236 
237  case wxT('"'):
238  result += wxT("&quot;");
239  break;
240 
241  case wxT('&'):
242  result += wxT("&amp;");
243  break;
244 
245  case wxT('<'):
246  result += wxT("&lt;");
247  break;
248 
249  case wxT('>'):
250  result += wxT("&gt;");
251  break;
252 
253  default:
254  if (sizeof(c) == 2 && c >= MIN_HIGH_SURROGATE && c <= MAX_HIGH_SURROGATE && i < len - 1) {
255  // If wxUChar is 2 bytes, then supplementary characters (those greater than U+FFFF) are represented
256  // with a high surrogate (U+D800..U+DBFF) followed by a low surrogate (U+DC00..U+DFFF).
257  // Handle those here.
258  wxUChar c2 = s.GetChar(++i);
259  if (c2 >= MIN_LOW_SURROGATE && c2 <= MAX_LOW_SURROGATE) {
260  // Surrogate pair found; simply add it to the output string.
261  result += c;
262  result += c2;
263  }
264  else {
265  // That high surrogate isn't paired, so ignore it.
266  i--;
267  }
268  }
269  else if (!wxIsprint(c)) {
270  //ignore several characters such ase eot (0x04) and stx (0x02) because it makes expat parser bail
271  //see xmltok.c in expat checkCharRefNumber() to see how expat bails on these chars.
272  //also see wxWidgets-2.8.12/src/expat/lib/asciitab.h to see which characters are nonxml compatible
273  //post decode (we can still encode '&' and '<' with this table, but it prevents us from encoding eot)
274  //everything is compatible past ascii 0x20 except for surrogates and the noncharacters U+FFFE and U+FFFF,
275  //so we don't check the compatibility table higher than this.
276  if((c> 0x1F || charXMLCompatiblity[c]!=0) &&
277  (c < MIN_HIGH_SURROGATE || c > MAX_LOW_SURROGATE) &&
279  result += wxString::Format(wxT("&#x%04x;"), c);
280  }
281  else {
282  result += c;
283  }
284  break;
285  }
286  }
287 
288  return result;
289 }
290 
295  const FilePath &outputPath, const TranslatableString &caption, bool keepBackup )
296  : mOutputPath{ outputPath }
297  , mCaption{ caption }
298  , mKeepBackup{ keepBackup }
299 // may throw
300 {
301  auto tempPath = wxFileName::CreateTempFileName( outputPath );
302  if (!wxFFile::Open(tempPath, wxT("wb")) || !IsOpened())
303  ThrowException( outputPath, mCaption );
304 
305  if (mKeepBackup) {
306  int index = 0;
307  wxString backupName;
308 
309  do {
310  wxFileName outputFn{ mOutputPath };
311  index++;
312  mBackupName =
313  outputFn.GetPath() + wxFILE_SEP_PATH +
314  outputFn.GetName() + wxT("_bak") +
315  wxString::Format(wxT("%d"), index) + wxT(".") +
316  outputFn.GetExt();
317  } while( ::wxFileExists( mBackupName ) );
318 
319  // Open the backup file to be sure we can write it and reserve it
320  // until committing
321  if (! mBackupFile.Open( mBackupName, "wb" ) || ! mBackupFile.IsOpened() )
322  ThrowException( mBackupName, mCaption );
323  }
324 }
325 
326 
328 {
329  // Don't let a destructor throw!
330  GuardedCall( [&] {
331  if (!mCommitted) {
332  auto fileName = GetName();
333  if ( IsOpened() )
335  ::wxRemoveFile( fileName );
336  }
337  } );
338 }
339 
341 // may throw
342 {
343  PreCommit();
344  PostCommit();
345 }
346 
348 // may throw
349 {
350  while (mTagstack.size()) {
351  EndTag(mTagstack[0]);
352  }
353 
355 }
356 
358 // may throw
359 {
360  FilePath tempPath = GetName();
361  if (mKeepBackup) {
362  if (! mBackupFile.Close() ||
363  ! wxRenameFile( mOutputPath, mBackupName ) )
365  }
366  else {
367  if ( wxFileName::FileExists( mOutputPath ) &&
368  ! wxRemoveFile( mOutputPath ) )
370  }
371 
372  // Now we have vacated the file at the output path and are committed.
373  // But not completely finished with steps of the commit operation.
374  // If this step fails, we haven't lost the successfully written data,
375  // but just failed to put it in the right place.
376  if (! wxRenameFile( tempPath, mOutputPath ) )
377  throw FileException{
379  };
380 
381  mCommitted = true;
382 }
383 
385 // may throw
386 {
387  // Before closing, we first flush it, because if Flush() fails because of a
388  // "disk full" condition, we can still at least try to close the file.
389  if (!wxFFile::Flush())
390  {
391  wxFFile::Close();
392  ThrowException( GetName(), mCaption );
393  }
394 
395  // Note that this should never fail if flushing worked.
396  if (!wxFFile::Close())
397  ThrowException( GetName(), mCaption );
398 }
399 
400 void XMLFileWriter::Write(const wxString &data)
401 // may throw
402 {
403  if (!wxFFile::Write(data, wxConvUTF8) || Error())
404  {
405  // When writing fails, we try to close the file before throwing the
406  // exception, so it can at least be deleted.
407  wxFFile::Close();
408  ThrowException( GetName(), mCaption );
409  }
410 }
411 
416 {
417  if (initialSize)
418  {
419  reserve(initialSize);
420  }
421 }
422 
424 {
425 }
426 
427 void XMLStringWriter::Write(const wxString &data)
428 {
429  Append(data);
430 }
XMLWriter::EndTag
virtual void EndTag(const wxString &name)
Definition: XMLWriter.cpp:99
XMLFileWriter::ThrowException
void ThrowException(const wxFileName &fileName, const TranslatableString &caption)
Definition: XMLWriter.h:113
TranslatableString
Holds a msgid for the translation catalog; may also bind format arguments.
Definition: TranslatableString.h:32
XMLFileWriter::XMLFileWriter
XMLFileWriter(const FilePath &outputPath, const TranslatableString &caption, bool keepBackup=false)
Definition: XMLWriter.cpp:294
GuardedCall
R GuardedCall(const F1 &body, const F2 &handler=F2::Default(), std::function< void(AudacityException *)> delayedHandler=DefaultDelayedHandlerAction{})
Execute some code on any thread; catch any AudacityException; enqueue error report on the main thread...
Definition: AudacityException.h:202
XMLFileWriter::mBackupFile
wxFFile mBackupFile
Definition: XMLWriter.h:128
XMLWriter.h
XMLFileWriter::~XMLFileWriter
virtual ~XMLFileWriter()
Definition: XMLWriter.cpp:327
MIN_LOW_SURROGATE
#define MIN_LOW_SURROGATE
Definition: XMLWriter.cpp:54
FileException::Cause::Rename
@ Rename
involves two filenames
NONCHARACTER_FFFF
#define NONCHARACTER_FFFF
Definition: XMLWriter.cpp:59
XMLStringWriter::~XMLStringWriter
virtual ~XMLStringWriter()
Definition: XMLWriter.cpp:423
XMLWriter::mInTag
bool mInTag
Definition: XMLWriter.h:62
XMLFileWriter::mCaption
const TranslatableString mCaption
Definition: XMLWriter.h:124
XMLFileWriter::Write
void Write(const wxString &data) override
Write to file. Might throw.
Definition: XMLWriter.cpp:400
FileException
Thrown for failure of file or database operations in deeply nested places.
Definition: FileException.h:19
charXMLCompatiblity
static int charXMLCompatiblity[]
Definition: XMLWriter.cpp:38
XMLFileWriter::PreCommit
void PreCommit()
Does the part of Commit that might fail because of exhaustion of space.
Definition: XMLWriter.cpp:347
XMLWriter::mHasKids
std::vector< int > mHasKids
Definition: XMLWriter.h:65
XMLFileWriter::mCommitted
bool mCommitted
Definition: XMLWriter.h:130
XMLFileWriter::mBackupName
FilePath mBackupName
Definition: XMLWriter.h:125
XMLWriter::mTagstack
wxArrayString mTagstack
Definition: XMLWriter.h:64
XMLWriter::XMLWriter
XMLWriter()
Definition: XMLWriter.cpp:65
FilePath
wxString FilePath
Definition: Project.h:20
XMLWriter::mDepth
int mDepth
Definition: XMLWriter.h:63
Append
Append([](My &table) -> Registry::BaseItemPtr { if(WaveTrackSubViews::slots() > 1) return 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=WaveTrackView::Get(track);menu.Check(id, view.GetMultiView());});else return nullptr;})
XMLStringWriter::Write
void Write(const wxString &data) override
Definition: XMLWriter.cpp:427
name
const TranslatableString name
Definition: Distortion.cpp:98
XMLFileWriter::PostCommit
void PostCommit()
Definition: XMLWriter.cpp:357
MAX_LOW_SURROGATE
#define MAX_LOW_SURROGATE
Definition: XMLWriter.cpp:55
XMLWriter::~XMLWriter
virtual ~XMLWriter()
Definition: XMLWriter.cpp:72
XMLWriter::Write
virtual void Write(const wxString &data)=0
NONCHARACTER_FFFE
#define NONCHARACTER_FFFE
Definition: XMLWriter.cpp:58
MAX_HIGH_SURROGATE
#define MAX_HIGH_SURROGATE
Definition: XMLWriter.cpp:53
XMLFileWriter::mOutputPath
const FilePath mOutputPath
Definition: XMLWriter.h:123
XMLWriter::WriteAttr
void WriteAttr(const wxString &name, const Identifier &value)
Definition: XMLWriter.h:34
XMLWriter::WriteSubTree
virtual void WriteSubTree(const wxString &value)
Definition: XMLWriter.cpp:211
XMLFileWriter::Commit
void Commit()
Definition: XMLWriter.cpp:340
XMLStringWriter::XMLStringWriter
XMLStringWriter(size_t initialSize=0)
Definition: XMLWriter.cpp:415
XMLFileWriter::CloseWithoutEndingTags
void CloseWithoutEndingTags()
Definition: XMLWriter.cpp:384
XMLWriter::WriteData
virtual void WriteData(const wxString &value)
Definition: XMLWriter.cpp:199
XMLFileWriter::mKeepBackup
const bool mKeepBackup
Definition: XMLWriter.h:126
MIN_HIGH_SURROGATE
#define MIN_HIGH_SURROGATE
Definition: XMLWriter.cpp:52
XMLWriter::StartTag
virtual void StartTag(const wxString &name)
Definition: XMLWriter.cpp:76
Internat::ToString
static wxString ToString(double numberToConvert, int digitsAfterDecimalPoint=-1)
Convert a number to a string, always uses the dot as decimal separator.
Definition: Internat.cpp:151
XMLWriter::XMLEsc
wxString XMLEsc(const wxString &s)
Definition: XMLWriter.cpp:224