/********************************************************************** Audacity: A Digital Audio Editor XMLWriter.cpp Leland Lucius *******************************************************************//** \class XMLWriter \brief Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating XML in UTF8 encoding. *//****************************************************************//** \class XMLFileWriter \brief Wrapper to output XML data to files. *//****************************************************************//** \class XMLStringWriter \brief Wrapper to output XML data to strings. *//*******************************************************************/ #include "../Audacity.h" #include #include #include #include #include "../Internat.h" #include "XMLWriter.h" //table for xml encoding compatibility with expat decoding //see wxWidgets-2.8.12/src/expat/lib/xmltok_impl.h //and wxWidgets-2.8.12/src/expat/lib/asciitab.h static int charXMLCompatiblity[] = { /* 0x00 */ 0, 0, 0, 0, /* 0x04 */ 0, 0, 0, 0, /* 0x08 */ 0, 1, 1, 0, /* 0x0C */ 0, 1, 0, 0, /* 0x10 */ 0, 0, 0, 0, /* 0x14 */ 0, 0, 0, 0, /* 0x18 */ 0, 0, 0, 0, /* 0x1C */ 0, 0, 0, 0, }; // These are used by XMLEsc to handle surrogate pairs and filter invalid characters outside the ASCII range. #define MIN_HIGH_SURROGATE static_cast(0xD800) #define MAX_HIGH_SURROGATE static_cast(0xDBFF) #define MIN_LOW_SURROGATE static_cast(0xDC00) #define MAX_LOW_SURROGATE static_cast(0xDFFF) // Unicode defines other noncharacters, but only these two are invalid in XML. #define NONCHARACTER_FFFE static_cast(0xFFFE) #define NONCHARACTER_FFFF static_cast(0xFFFF) /// /// XMLWriter base class /// XMLWriter::XMLWriter() { mDepth = 0; mInTag = false; mHasKids.push_back(false); } XMLWriter::~XMLWriter() { } void XMLWriter::StartTag(const wxString &name) // may throw { int i; if (mInTag) { Write(wxT(">\n")); mInTag = false; } for (i = 0; i < mDepth; i++) { Write(wxT("\t")); } Write(wxString::Format(wxT("<%s"), name)); mTagstack.Insert(name, 0); mHasKids[0] = true; mHasKids.insert(mHasKids.begin(), false); mDepth++; mInTag = true; } void XMLWriter::EndTag(const wxString &name) // may throw { int i; if (mTagstack.GetCount() > 0) { if (mTagstack[0] == name) { if (mHasKids[1]) { // There will always be at least 2 at this point if (mInTag) { Write(wxT("/>\n")); } else { for (i = 0; i < mDepth - 1; i++) { Write(wxT("\t")); } Write(wxString::Format(wxT("\n"), name)); } } else { Write(wxT(">\n")); } mTagstack.RemoveAt(0); mHasKids.erase(mHasKids.begin()); } } mDepth--; mInTag = false; } void XMLWriter::WriteAttr(const wxString &name, const wxString &value) // may throw from Write() { Write(wxString::Format(wxT(" %s=\"%s\""), name, XMLEsc(value))); } void XMLWriter::WriteAttr(const wxString &name, const wxChar *value) // may throw from Write() { WriteAttr(name, wxString(value)); } void XMLWriter::WriteAttr(const wxString &name, int value) // may throw from Write() { Write(wxString::Format(wxT(" %s=\"%d\""), name, value)); } void XMLWriter::WriteAttr(const wxString &name, bool value) // may throw from Write() { Write(wxString::Format(wxT(" %s=\"%d\""), name, value)); } void XMLWriter::WriteAttr(const wxString &name, long value) // may throw from Write() { Write(wxString::Format(wxT(" %s=\"%ld\""), name, value)); } void XMLWriter::WriteAttr(const wxString &name, long long value) // may throw from Write() { Write(wxString::Format(wxT(" %s=\"%lld\""), name, value)); } void XMLWriter::WriteAttr(const wxString &name, size_t value) // may throw from Write() { Write(wxString::Format(wxT(" %s=\"%lld\""), name, (long long) value)); } void XMLWriter::WriteAttr(const wxString &name, float value, int digits) // may throw from Write() { Write(wxString::Format(wxT(" %s=\"%s\""), name, Internat::ToString(value, digits))); } void XMLWriter::WriteAttr(const wxString &name, double value, int digits) // may throw from Write() { Write(wxString::Format(wxT(" %s=\"%s\""), name, Internat::ToString(value, digits))); } void XMLWriter::WriteData(const wxString &value) // may throw from Write() { int i; for (i = 0; i < mDepth; i++) { Write(wxT("\t")); } Write(XMLEsc(value)); } void XMLWriter::WriteSubTree(const wxString &value) // may throw from Write() { if (mInTag) { Write(wxT(">\n")); mInTag = false; mHasKids[0] = true; } Write(value); } // See http://www.w3.org/TR/REC-xml for reference wxString XMLWriter::XMLEsc(const wxString & s) { wxString result; int len = s.Length(); for(int i=0; i'): result += wxT(">"); break; default: if (sizeof(c) == 2 && c >= MIN_HIGH_SURROGATE && c <= MAX_HIGH_SURROGATE && i < len - 1) { // If wxUChar is 2 bytes, then supplementary characters (those greater than U+FFFF) are represented // with a high surrogate (U+D800..U+DBFF) followed by a low surrogate (U+DC00..U+DFFF). // Handle those here. wxUChar c2 = s.GetChar(++i); if (c2 >= MIN_LOW_SURROGATE && c2 <= MAX_LOW_SURROGATE) { // Surrogate pair found; simply add it to the output string. result += c; result += c2; } else { // That high surrogate isn't paired, so ignore it. i--; } } else if (!wxIsprint(c)) { //ignore several characters such ase eot (0x04) and stx (0x02) because it makes expat parser bail //see xmltok.c in expat checkCharRefNumber() to see how expat bails on these chars. //also see wxWidgets-2.8.12/src/expat/lib/asciitab.h to see which characters are nonxml compatible //post decode (we can still encode '&' and '<' with this table, but it prevents us from encoding eot) //everything is compatible past ascii 0x20 except for surrogates and the noncharacters U+FFFE and U+FFFF, //so we don't check the compatibility table higher than this. if((c> 0x1F || charXMLCompatiblity[c]!=0) && (c < MIN_HIGH_SURROGATE || c > MAX_LOW_SURROGATE) && c != NONCHARACTER_FFFE && c != NONCHARACTER_FFFF) result += wxString::Format(wxT("&#x%04x;"), c); } else { result += c; } break; } } return result; } /// /// XMLFileWriter class /// XMLFileWriter::XMLFileWriter ( const wxString &outputPath, const wxString &caption, bool keepBackup ) : mOutputPath{ outputPath } , mCaption{ caption } , mKeepBackup{ keepBackup } // may throw { auto tempPath = wxFileName::CreateTempFileName( outputPath ); if (!wxFFile::Open(tempPath, wxT("wb")) || !IsOpened()) ThrowException( tempPath, mCaption ); if (mKeepBackup) { int index = 0; wxString backupName; do { wxFileName outputFn{ mOutputPath }; index++; mBackupName = outputFn.GetPath() + wxFILE_SEP_PATH + outputFn.GetName() + wxT("_bak") + wxString::Format(wxT("%d"), index) + wxT(".") + outputFn.GetExt(); } while( ::wxFileExists( mBackupName ) ); // Open the backup file to be sure we can write it and reserve it // until committing if (! mBackupFile.Open( mBackupName, "wb" ) || ! mBackupFile.IsOpened() ) ThrowException( mBackupName, mCaption ); } } XMLFileWriter::~XMLFileWriter() { // Don't let a destructor throw! GuardedCall( [&] { if (!mCommitted) { auto fileName = GetName(); if ( IsOpened() ) CloseWithoutEndingTags(); ::wxRemoveFile( fileName ); } } ); } void XMLFileWriter::Commit() // may throw { PreCommit(); PostCommit(); } void XMLFileWriter::PreCommit() // may throw { while (mTagstack.GetCount()) { EndTag(mTagstack[0]); } CloseWithoutEndingTags(); } void XMLFileWriter::PostCommit() // may throw { auto tempPath = GetName(); if (mKeepBackup) { if (! mBackupFile.Close() || ! wxRenameFile( mOutputPath, mBackupName ) ) ThrowException( mBackupName, mCaption ); } else { if ( wxFileName::FileExists( mOutputPath ) && ! wxRemoveFile( mOutputPath ) ) ThrowException( mOutputPath, mCaption ); } // Now we have vacated the file at the output path and are committed. // But not completely finished with steps of the commit operation. // If this step fails, we haven't lost the successfully written data, // but just failed to put it in the right place. if (! wxRenameFile( tempPath, mOutputPath ) ) throw FileException{ FileException::Cause::Rename, tempPath, mCaption, mOutputPath }; mCommitted = true; } void XMLFileWriter::CloseWithoutEndingTags() // may throw { // Before closing, we first flush it, because if Flush() fails because of a // "disk full" condition, we can still at least try to close the file. if (!wxFFile::Flush()) { wxFFile::Close(); ThrowException( GetName(), mCaption ); } // Note that this should never fail if flushing worked. if (!wxFFile::Close()) ThrowException( GetName(), mCaption ); } void XMLFileWriter::Write(const wxString &data) // may throw { if (!wxFFile::Write(data, wxConvUTF8) || Error()) { // When writing fails, we try to close the file before throwing the // exception, so it can at least be deleted. wxFFile::Close(); ThrowException( GetName(), mCaption ); } } /// /// XMLStringWriter class /// XMLStringWriter::XMLStringWriter(size_t initialSize) { if (initialSize) { Alloc(initialSize); } } XMLStringWriter::~XMLStringWriter() { } void XMLStringWriter::Write(const wxString &data) { Append(data); }