433 lines
11 KiB
C++
433 lines
11 KiB
C++
/**********************************************************************
|
|
|
|
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 <wx/defs.h>
|
|
#include <wx/ffile.h>
|
|
#include <wx/intl.h>
|
|
|
|
#include <string.h>
|
|
|
|
#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<wxUChar>(0xD800)
|
|
#define MAX_HIGH_SURROGATE static_cast<wxUChar>(0xDBFF)
|
|
#define MIN_LOW_SURROGATE static_cast<wxUChar>(0xDC00)
|
|
#define MAX_LOW_SURROGATE static_cast<wxUChar>(0xDFFF)
|
|
|
|
// Unicode defines other noncharacters, but only these two are invalid in XML.
|
|
#define NONCHARACTER_FFFE static_cast<wxUChar>(0xFFFE)
|
|
#define NONCHARACTER_FFFF static_cast<wxUChar>(0xFFFF)
|
|
|
|
|
|
///
|
|
/// XMLWriter base class
|
|
///
|
|
XMLWriter::XMLWriter()
|
|
{
|
|
mDepth = 0;
|
|
mInTag = false;
|
|
mHasKids.Add(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(false, 0);
|
|
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("</%s>\n"), name));
|
|
}
|
|
}
|
|
else {
|
|
Write(wxT(">\n"));
|
|
}
|
|
mTagstack.RemoveAt(0);
|
|
mHasKids.RemoveAt(0);
|
|
}
|
|
}
|
|
|
|
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<len; i++) {
|
|
wxUChar c = s.GetChar(i);
|
|
|
|
switch (c) {
|
|
case wxT('\''):
|
|
result += wxT("'");
|
|
break;
|
|
|
|
case wxT('"'):
|
|
result += wxT(""");
|
|
break;
|
|
|
|
case wxT('&'):
|
|
result += wxT("&");
|
|
break;
|
|
|
|
case wxT('<'):
|
|
result += wxT("<");
|
|
break;
|
|
|
|
case wxT('>'):
|
|
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);
|
|
}
|