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