/********************************************************************** Audacity: A Digital Audio Editor XMLFileReader.cpp Dominic Mazzoni *******************************************************************//** \class XMLFileReader \brief Reads a file and passes the results through an XMLTagHandler. *//*******************************************************************/ #include "XMLFileReader.h" #include #include #include #include #include "expat.h" XMLFileReader::XMLFileReader() { mParser = XML_ParserCreate(NULL); XML_SetUserData(mParser, (void *)this); XML_SetElementHandler(mParser, startElement, endElement); XML_SetCharacterDataHandler(mParser, charHandler); mBaseHandler = NULL; mHandler.reserve(128); } XMLFileReader::~XMLFileReader() { XML_ParserFree(mParser); } bool XMLFileReader::Parse(XMLTagHandler *baseHandler, const FilePath &fname) { wxFFile theXMLFile(fname, wxT("rb")); if (!theXMLFile.IsOpened()) { mErrorStr = XO("Could not open file: \"%s\"").Format( fname ); return false; } mBaseHandler = baseHandler; const size_t bufferSize = 16384; char buffer[16384]; int done = 0; do { size_t len = fread(buffer, 1, bufferSize, theXMLFile.fp()); done = (len < bufferSize); if (!XML_Parse(mParser, buffer, len, done)) { // Embedded error string from expat doesn't translate (yet) // We could make a table of XOs if we wanted so that it could // If we do, uncomment the second constructor argument so it's not // a verbatim string mLibraryErrorStr = Verbatim( XML_ErrorString(XML_GetErrorCode(mParser)) // , {} ); mErrorStr = XO("Error: %s at line %lu").Format( mLibraryErrorStr, (long unsigned int)XML_GetCurrentLineNumber(mParser) ); theXMLFile.Close(); return false; // If we did want to handle every single parse error, these are they.... /* XML_L("out of memory"), XML_L("syntax error"), XML_L("no element found"), XML_L("not well-formed (invalid token)"), XML_L("unclosed token"), XML_L("partial character"), XML_L("mismatched tag"), XML_L("duplicate attribute"), XML_L("junk after document element"), XML_L("illegal parameter entity reference"), XML_L("undefined entity"), XML_L("recursive entity reference"), XML_L("asynchronous entity"), XML_L("reference to invalid character number"), XML_L("reference to binary entity"), XML_L("reference to external entity in attribute"), XML_L("XML or text declaration not at start of entity"), XML_L("unknown encoding"), XML_L("encoding specified in XML declaration is incorrect"), XML_L("unclosed CDATA section"), XML_L("error in processing external entity reference"), XML_L("document is not standalone"), XML_L("unexpected parser state - please send a bug report"), XML_L("entity declared in parameter entity"), XML_L("requested feature requires XML_DTD support in Expat"), XML_L("cannot change setting once parsing has begun"), XML_L("unbound prefix"), XML_L("must not undeclare prefix"), XML_L("incomplete markup in parameter entity"), XML_L("XML declaration not well-formed"), XML_L("text declaration not well-formed"), XML_L("illegal character(s) in public id"), XML_L("parser suspended"), XML_L("parser not suspended"), XML_L("parsing aborted"), XML_L("parsing finished"), XML_L("cannot suspend in external parameter entity"), XML_L("reserved prefix (xml) must not be undeclared or bound to another namespace name"), XML_L("reserved prefix (xmlns) must not be declared or undeclared"), XML_L("prefix must not be bound to one of the reserved namespace names") */ } } while (!done); theXMLFile.Close(); // Even though there were no parse errors, we only succeed if // the first-level handler actually got called, and didn't // return false. if (mBaseHandler) return true; else { mErrorStr = XO("Could not load file: \"%s\"").Format( fname ); return false; } } bool XMLFileReader::ParseString(XMLTagHandler *baseHandler, const wxString &xmldata) { auto utf8 = xmldata.ToUTF8(); const char *buffer = utf8.data(); int len = utf8.length(); mBaseHandler = baseHandler; if (!XML_Parse(mParser, buffer, len, true)) { // Embedded error string from expat doesn't translate (yet) // We could make a table of XOs if we wanted so that it could // If we do, uncomment the second constructor argument so it's not // a verbatim string mLibraryErrorStr = Verbatim( XML_ErrorString(XML_GetErrorCode(mParser)) // , {} ); mErrorStr = XO("Error: %s at line %lu").Format( mLibraryErrorStr, (long unsigned int)XML_GetCurrentLineNumber(mParser) ); wxLogMessage(wxT("ParseString error: %s\n===begin===%s\n===end==="), mErrorStr.Debug(), buffer); return false; } // Even though there were no parse errors, we only succeed if // the first-level handler actually got called, and didn't // return false. if (!mBaseHandler) { mErrorStr = XO("Could not parse XML"); return false; } return true; } const TranslatableString &XMLFileReader::GetErrorStr() const { return mErrorStr; } const TranslatableString &XMLFileReader::GetLibraryErrorStr() const { return mLibraryErrorStr; } // static void XMLFileReader::startElement(void *userData, const char *name, const char **atts) { XMLFileReader *This = (XMLFileReader *)userData; Handlers &handlers = This->mHandler; if (handlers.empty()) { handlers.push_back(This->mBaseHandler); } else { if (XMLTagHandler *const handler = handlers.back()) handlers.push_back(handler->ReadXMLChild(name)); else handlers.push_back(NULL); } if (XMLTagHandler *& handler = handlers.back()) { if (!handler->ReadXMLTag(name, atts)) { handler = nullptr; if (handlers.size() == 1) This->mBaseHandler = nullptr; } } } // static void XMLFileReader::endElement(void *userData, const char *name) { XMLFileReader *This = (XMLFileReader *)userData; Handlers &handlers = This->mHandler; if (XMLTagHandler *const handler = handlers.back()) handler->ReadXMLEndTag(name); handlers.pop_back(); } // static void XMLFileReader::charHandler(void *userData, const char *s, int len) { XMLFileReader *This = (XMLFileReader *)userData; Handlers &handlers = This->mHandler; if (XMLTagHandler *const handler = handlers.back()) handler->ReadXMLContent(s, len); }