/*************************************************************************** * Copyright (c) 2011 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ #include #include #include #include #include #include #endif #include #include "Reader.h" #include "Base64.h" #include "Base64Filter.h" #include "Console.h" #include "Exception.h" #include "InputSource.h" #include "Persistence.h" #include "Sequencer.h" #include "Stream.h" #include "XMLTools.h" #ifdef _MSC_VER #include #endif #include #include #ifndef XERCES_CPP_NAMESPACE_BEGIN #define XERCES_CPP_NAMESPACE_QUALIFIER using namespace XERCES_CPP_NAMESPACE; #else XERCES_CPP_NAMESPACE_USE #endif using namespace std; // --------------------------------------------------------------------------- // Base::XMLReader: Constructors and Destructor // --------------------------------------------------------------------------- Base::XMLReader::XMLReader(const char* FileName, std::istream& str) : _File(FileName) { #ifdef _MSC_VER str.imbue(std::locale::empty()); #else str.imbue(std::locale::classic()); #endif // create the parser parser = XMLReaderFactory::createXMLReader(); // NOLINT parser->setContentHandler(this); parser->setLexicalHandler(this); parser->setErrorHandler(this); try { StdInputSource file(str, _File.filePath().c_str()); _valid = parser->parseFirst(file, token); } catch (const XMLException& toCatch) { char* message = XMLString::transcode(toCatch.getMessage()); cerr << "Exception message is: \n" << message << "\n"; XMLString::release(&message); } catch (const SAXParseException& toCatch) { char* message = XMLString::transcode(toCatch.getMessage()); cerr << "Exception message is: \n" << message << "\n"; XMLString::release(&message); } #ifndef FC_DEBUG catch (...) { cerr << "Unexpected Exception \n"; } #endif } Base::XMLReader::~XMLReader() { // Delete the parser itself. Must be done prior to calling Terminate, below. delete parser; } const char* Base::XMLReader::localName() const { return LocalName.c_str(); } unsigned int Base::XMLReader::getAttributeCount() const { return static_cast(AttrMap.size()); } namespace { template T readerCast(const char* value) { if constexpr (std::is_same_v) { return value; } if constexpr (std::is_same_v) { return stol(value); } if constexpr (std::is_same_v) { return stoi(value); } if constexpr (std::is_same_v) { return stoul(value, nullptr); } if constexpr (std::is_same_v) { return stod(value, nullptr); } if constexpr (std::is_same_v) { return std::string_view(value) != "0"; } } } // anonymous namespace template requires Base::XMLReader::instantiated T Base::XMLReader::getAttribute(const char* AttrName, T defaultValue) const { auto pos = AttrMap.find(AttrName); if (pos == AttrMap.end()) { return defaultValue; } const char* rawValue = pos->second.c_str(); return readerCast(rawValue); } template requires Base::XMLReader::instantiated T Base::XMLReader::getAttribute(const char* AttrName) const { auto pos = AttrMap.find(AttrName); if (pos == AttrMap.end()) { // wrong name, use hasAttribute if not sure! std::string msg = std::string("XML Attribute: \"") + AttrName + "\" not found"; throw Base::XMLAttributeError(msg); } const char* rawValue = pos->second.c_str(); return readerCast(rawValue); } // Explicit template instantiation template BaseExport bool Base::XMLReader::getAttribute(const char* AttrName, bool defaultValue) const; template BaseExport bool Base::XMLReader::getAttribute(const char* AttrName) const; template BaseExport const char* Base::XMLReader::getAttribute(const char* AttrName, const char* defaultValue) const; template BaseExport const char* Base::XMLReader::getAttribute(const char* AttrName) const; template BaseExport double Base::XMLReader::getAttribute(const char* AttrName, double defaultValue) const; template BaseExport double Base::XMLReader::getAttribute(const char* AttrName) const; template BaseExport int Base::XMLReader::getAttribute(const char* AttrName, int defaultValue) const; template BaseExport int Base::XMLReader::getAttribute(const char* AttrName) const; template BaseExport long Base::XMLReader::getAttribute(const char* AttrName, long defaultValue) const; template BaseExport long Base::XMLReader::getAttribute(const char* AttrName) const; template BaseExport unsigned long Base::XMLReader::getAttribute(const char* AttrName, unsigned long defaultValue) const; template BaseExport unsigned long Base::XMLReader::getAttribute(const char* AttrName) const; bool Base::XMLReader::hasAttribute(const char* AttrName) const { return AttrMap.find(AttrName) != AttrMap.end(); } bool Base::XMLReader::read() { ReadType = None; try { parser->parseNext(token); } catch (const XMLException& toCatch) { char* message = XMLString::transcode(toCatch.getMessage()); std::string what = message; XMLString::release(&message); throw Base::XMLBaseException(what); } catch (const SAXParseException& toCatch) { char* message = XMLString::transcode(toCatch.getMessage()); std::string what = message; XMLString::release(&message); throw Base::XMLParseException(what); } catch (...) { throw Base::XMLBaseException("Unexpected XML exception"); } return true; } void Base::XMLReader::readElement(const char* ElementName) { bool ok {}; endCharStream(); int currentLevel = Level; std::string currentName = LocalName; do { ok = read(); if (!ok) { break; } if (ReadType == EndElement && currentName == LocalName && currentLevel >= Level) { // we have reached the end of the element when calling this method // thus we must stop reading on. break; } if (ReadType == EndDocument) { // the end of the document has been reached but we still try to continue on reading throw Base::XMLParseException("End of document reached"); } } while ((ReadType != StartElement && ReadType != StartEndElement) || (ElementName && LocalName != ElementName)); } bool Base::XMLReader::readNextElement() { bool ok {}; while (true) { ok = read(); if (!ok) { break; } if (ReadType == StartElement) { break; } if (ReadType == StartEndElement) { break; } if (ReadType == EndElement) { break; } if (ReadType == EndDocument) { break; } }; return (ReadType == StartElement || ReadType == StartEndElement); } int Base::XMLReader::level() const { return Level; } bool Base::XMLReader::isEndOfElement() const { return (ReadType == EndElement); } bool Base::XMLReader::isStartOfDocument() const { return (ReadType == StartDocument); } bool Base::XMLReader::isEndOfDocument() const { return (ReadType == EndDocument); } void Base::XMLReader::readEndElement(const char* ElementName, int level) { endCharStream(); // if we are already at the end of the current element if ((ReadType == EndElement || ReadType == StartEndElement) && ElementName && LocalName == ElementName && (level < 0 || level == Level)) { return; } if (ReadType == EndDocument) { // the end of the document has been reached but we still try to continue on reading throw Base::XMLParseException("End of document reached"); } bool ok {}; do { ok = read(); if (!ok) { break; } if (ReadType == EndDocument) { break; } } while (ReadType != EndElement || (ElementName && (LocalName != ElementName || (level >= 0 && level != Level)))); } void Base::XMLReader::readCharacters(const char* filename, CharStreamFormat format) { Base::FileInfo fi(filename); Base::ofstream to(fi, std::ios::out | std::ios::binary | std::ios::trunc); if (!to) { throw Base::FileException("XMLReader::readCharacters() Could not open file!"); } beginCharStream(format) >> to.rdbuf(); to.close(); endCharStream(); } std::streamsize Base::XMLReader::read(char_type* s, std::streamsize n) { char_type* buf = s; if (CharacterOffset < 0) { return -1; } for (;;) { std::streamsize copy_size = static_cast(Characters.size()) - CharacterOffset; if (n < copy_size) { copy_size = n; } std::memcpy(s, Characters.c_str() + CharacterOffset, copy_size); n -= copy_size; s += copy_size; CharacterOffset += copy_size; if (!n) { break; } if (ReadType == Chars) { read(); } else { CharacterOffset = -1; break; } } return s - buf; } void Base::XMLReader::endCharStream() { CharacterOffset = -1; CharStream.reset(); } std::istream& Base::XMLReader::charStream() { if (!CharStream) { throw Base::XMLParseException("no current character stream"); } return *CharStream; } std::istream& Base::XMLReader::beginCharStream(CharStreamFormat format) { if (CharStream) { throw Base::XMLParseException("recursive character stream"); } // TODO: An XML element can actually contain a mix of child elements and // characters. So we should not actually demand 'StartElement' here. But // with the current implementation of character stream, we cannot track // child elements and character content at the same time. if (ReadType == StartElement) { CharacterOffset = 0; read(); } else if (ReadType == StartEndElement) { // If we are currently at a self-closing element, just leave the offset // as negative and do not read any characters. This will result in an // empty input stream for the caller. CharacterOffset = -1; } else { throw Base::XMLParseException("invalid state while reading character stream"); } CharStream = std::make_unique(); auto* filteringStream = dynamic_cast(CharStream.get()); if (format == CharStreamFormat::Base64Encoded) { filteringStream->push( base64_decoder(Base::base64DefaultBufferSize, Base64ErrorHandling::silent)); } filteringStream->push(boost::ref(*this)); return *CharStream; } void Base::XMLReader::readBinFile(const char* filename) { Base::FileInfo fi(filename); Base::ofstream to(fi, std::ios::out | std::ios::binary); if (!to) { throw Base::FileException("XMLReader::readBinFile() Could not open file!"); } bool ok {}; do { ok = read(); if (!ok) { break; } } while (ReadType != EndCDATA); to << Base::base64_decode(Characters); to.close(); } void Base::XMLReader::readFiles(zipios::ZipInputStream& zipstream) const { // It's possible that not all objects inside the document could be created, e.g. if a module // is missing that would know these object types. So, there may be data files inside the zip // file that cannot be read. We simply ignore these files. // On the other hand, however, it could happen that a file should be read that is not part of // the zip file. This happens e.g. if a document is written without GUI up but is read with GUI // up. In this case the associated GUI document asks for its file which is not part of the ZIP // file, then. // In either case it's guaranteed that the order of the files is kept. zipios::ConstEntryPointer entry; try { entry = zipstream.getNextEntry(); } catch (const std::exception&) { // There is no further file at all. This can happen if the // project file was created without GUI return; } std::vector::const_iterator it = FileList.begin(); Base::SequencerLauncher seq("Importing project files...", FileList.size()); while (entry->isValid() && it != FileList.end()) { std::vector::const_iterator jt = it; // Check if the current entry is registered, otherwise check the next registered files as // soon as both file names match while (jt != FileList.end() && entry->getName() != jt->FileName) { ++jt; } // If this condition is true both file names match and we can read-in the data, otherwise // no file name for the current entry in the zip was registered. if (jt != FileList.end()) { try { Base::Reader reader(zipstream, jt->FileName, FileVersion); jt->Object->RestoreDocFile(reader); if (reader.getLocalReader()) { reader.getLocalReader()->readFiles(zipstream); } } catch (...) { // For any exception we just continue with the next file. // It doesn't matter if the last reader has read more or // less data than the file size would allow. // All what we need to do is to notify the user about the // failure. Base::Console().error("Reading failed from embedded file: %s\n", entry->toString().c_str()); FailedFiles.push_back(jt->FileName); } // Go to the next registered file name it = jt + 1; } seq.next(); // In either case we must go to the next entry try { entry = zipstream.getNextEntry(); } catch (const std::exception&) { // there is no further entry break; } } } const char* Base::XMLReader::addFile(const char* Name, Base::Persistence* Object) { FileEntry temp; temp.FileName = Name; temp.Object = Object; FileList.push_back(temp); return Name; } bool Base::XMLReader::hasFilenames() const { return !FileList.empty(); } bool Base::XMLReader::hasReadFailed(const std::string& filename) const { return std::ranges::find(FailedFiles, filename) != FailedFiles.end(); } bool Base::XMLReader::isRegistered(Base::Persistence* Object) const { if (Object) { for (const auto& it : FileList) { if (it.Object == Object) { return true; } } } return false; } void Base::XMLReader::addName(const char* /*unused*/, const char* /*unused*/) {} const char* Base::XMLReader::getName(const char* name) const { return name; } bool Base::XMLReader::doNameMapping() const { return false; } // --------------------------------------------------------------------------- // Base::XMLReader: Implementation of the SAX DocumentHandler interface // --------------------------------------------------------------------------- void Base::XMLReader::startDocument() { ReadType = StartDocument; } void Base::XMLReader::endDocument() { ReadType = EndDocument; } void Base::XMLReader::startElement(const XMLCh* const /*uri*/, const XMLCh* const localname, const XMLCh* const /*qname*/, const XERCES_CPP_NAMESPACE_QUALIFIER Attributes& attrs) { Level++; // new scope LocalName = StrX(localname).c_str(); // saving attributes of the current scope, delete all previously stored ones AttrMap.clear(); for (unsigned int i = 0; i < attrs.getLength(); i++) { AttrMap[StrX(attrs.getQName(i)).c_str()] = StrXUTF8(attrs.getValue(i)).c_str(); } ReadType = StartElement; } void Base::XMLReader::endElement(const XMLCh* const /*uri*/, const XMLCh* const localname, const XMLCh* const /*qname*/) { Level--; // end of scope LocalName = StrX(localname).c_str(); if (ReadType == StartElement) { ReadType = StartEndElement; } else { ReadType = EndElement; } } void Base::XMLReader::startCDATA() { ReadType = StartCDATA; } void Base::XMLReader::endCDATA() { ReadType = EndCDATA; } void Base::XMLReader::characters(const XMLCh* const chars, const XMLSize_t length) { Characters = StrX(chars).c_str(); ReadType = Chars; CharacterCount += length; } void Base::XMLReader::ignorableWhitespace(const XMLCh* const /*chars*/, const XMLSize_t /*length*/) { // fSpaceCount += length; } void Base::XMLReader::resetDocument() { // fAttrCount = 0; // fCharacterCount = 0; // fElementCount = 0; // fSpaceCount = 0; } // --------------------------------------------------------------------------- // Base::XMLReader: Overrides of the SAX ErrorHandler interface // --------------------------------------------------------------------------- void Base::XMLReader::error(const XERCES_CPP_NAMESPACE_QUALIFIER SAXParseException& e) { // print some details to error output and throw an // exception to abort the parsing cerr << "Error at file " << StrX(e.getSystemId()) << ", line " << e.getLineNumber() << ", char " << e.getColumnNumber() << endl; throw e; } void Base::XMLReader::fatalError(const XERCES_CPP_NAMESPACE_QUALIFIER SAXParseException& e) { // print some details to error output and throw an // exception to abort the parsing cerr << "Fatal Error at file " << StrX(e.getSystemId()) << ", line " << e.getLineNumber() << ", char " << e.getColumnNumber() << endl; throw e; } void Base::XMLReader::warning(const XERCES_CPP_NAMESPACE_QUALIFIER SAXParseException& e) { // print some details to error output and throw an // exception to abort the parsing cerr << "Warning at file " << StrX(e.getSystemId()) << ", line " << e.getLineNumber() << ", char " << e.getColumnNumber() << endl; throw e; } void Base::XMLReader::resetErrors() {} bool Base::XMLReader::testStatus(ReaderStatus pos) const { return StatusBits.test(static_cast(pos)); } void Base::XMLReader::setStatus(ReaderStatus pos, bool on) { StatusBits.set(static_cast(pos), on); } void Base::XMLReader::setPartialRestore(bool on) { setStatus(PartialRestore, on); setStatus(PartialRestoreInDocumentObject, on); setStatus(PartialRestoreInProperty, on); setStatus(PartialRestoreInObject, on); } void Base::XMLReader::clearPartialRestoreDocumentObject() { setStatus(PartialRestoreInDocumentObject, false); setStatus(PartialRestoreInProperty, false); setStatus(PartialRestoreInObject, false); } void Base::XMLReader::clearPartialRestoreProperty() { setStatus(PartialRestoreInProperty, false); setStatus(PartialRestoreInObject, false); } void Base::XMLReader::clearPartialRestoreObject() { setStatus(PartialRestoreInObject, false); } // ---------------------------------------------------------- // NOLINTNEXTLINE Base::Reader::Reader(std::istream& str, const std::string& name, int version) : std::istream(str.rdbuf()) , _str(str) , _name(name) , fileVersion(version) {} std::string Base::Reader::getFileName() const { return this->_name; } int Base::Reader::getFileVersion() const { return fileVersion; } std::istream& Base::Reader::getStream() { return this->_str; } void Base::Reader::initLocalReader(std::shared_ptr reader) { this->localreader = reader; } std::shared_ptr Base::Reader::getLocalReader() const { return (this->localreader); }