diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
commit | 8362bf63dea22bbf6736609b0f49c152f975eb63 (patch) | |
tree | 0eea3928e39e50fae91d4e68b21b1e6cbae25604 /lib/store | |
download | koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.tar.gz koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.zip |
Added old abandoned KDE3 version of koffice
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/koffice@1077364 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'lib/store')
-rw-r--r-- | lib/store/KoDirectoryStore.cpp | 117 | ||||
-rw-r--r-- | lib/store/KoDirectoryStore.h | 54 | ||||
-rw-r--r-- | lib/store/KoStore.cpp | 629 | ||||
-rw-r--r-- | lib/store/KoStore.h | 381 | ||||
-rw-r--r-- | lib/store/KoStoreBase.cpp | 29 | ||||
-rw-r--r-- | lib/store/KoStoreBase.h | 51 | ||||
-rw-r--r-- | lib/store/KoStoreDevice.h | 88 | ||||
-rw-r--r-- | lib/store/KoStoreDrag.cpp | 35 | ||||
-rw-r--r-- | lib/store/KoStoreDrag.h | 55 | ||||
-rw-r--r-- | lib/store/KoTarStore.cpp | 206 | ||||
-rw-r--r-- | lib/store/KoTarStore.h | 65 | ||||
-rw-r--r-- | lib/store/KoXmlWriter.cpp | 427 | ||||
-rw-r--r-- | lib/store/KoXmlWriter.h | 281 | ||||
-rw-r--r-- | lib/store/KoZipStore.cpp | 237 | ||||
-rw-r--r-- | lib/store/KoZipStore.h | 61 | ||||
-rw-r--r-- | lib/store/Makefile.am | 13 | ||||
-rw-r--r-- | lib/store/SPEC | 122 | ||||
-rw-r--r-- | lib/store/fix_storage.pl | 217 | ||||
-rw-r--r-- | lib/store/tests/Makefile.am | 18 | ||||
-rw-r--r-- | lib/store/tests/storage_test.cpp | 220 | ||||
-rw-r--r-- | lib/store/tests/storedroptest.cpp | 138 | ||||
-rw-r--r-- | lib/store/tests/xmlwritertest.cpp | 144 | ||||
-rw-r--r-- | lib/store/tests/xmlwritertest.h | 46 | ||||
-rw-r--r-- | lib/store/update_kzip.sh | 26 |
24 files changed, 3660 insertions, 0 deletions
diff --git a/lib/store/KoDirectoryStore.cpp b/lib/store/KoDirectoryStore.cpp new file mode 100644 index 00000000..d5b698f0 --- /dev/null +++ b/lib/store/KoDirectoryStore.cpp @@ -0,0 +1,117 @@ +/* This file is part of the KDE project + Copyright (C) 2002, 2006 David Faure <faure@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "KoDirectoryStore.h" +#include <qfile.h> +#include <qdir.h> +#include <kdebug.h> + +// HMMM... I used QFile and QDir.... but maybe this should be made network transparent? + +KoDirectoryStore::KoDirectoryStore( const QString& path, Mode _mode ) + : m_basePath( path ) +{ + const int pos = path.findRev( '/' ); + // The parameter must include "maindoc.xml" or "content.xml" + if ( pos != -1 && pos != (int)m_basePath.length()-1 ) + m_basePath = m_basePath.left( pos ); + if ( !m_basePath.endsWith("/") ) + m_basePath += '/'; + m_currentPath = m_basePath; + kdDebug(s_area) << "KoDirectoryStore::KoDirectoryStore base path:" << m_basePath << endl; + m_bGood = init( _mode ); +} + +KoDirectoryStore::~KoDirectoryStore() +{ +} + +bool KoDirectoryStore::init( Mode _mode ) +{ + KoStore::init( _mode ); + QDir dir( m_basePath ); + if ( dir.exists() ) + return true; + dir = QDir::current(); + // Dir doesn't exist. If reading -> error. If writing -> create. + if ( _mode == Write && dir.mkdir( m_basePath ) ) { + kdDebug(s_area) << "KoDirectoryStore::init Directory created: " << m_basePath << endl; + return true; + } + return false; +} + +bool KoDirectoryStore::openReadOrWrite( const QString& name, int iomode ) +{ + //kdDebug(s_area) << "KoDirectoryStore::openReadOrWrite m_currentPath=" << m_currentPath << " name=" << name << endl; + int pos = name.findRev('/'); + if ( pos != -1 ) // there are subdirs in the name -> maybe need to create them, when writing + { + pushDirectory(); // remember where we were + enterAbsoluteDirectory( QString::null ); + //kdDebug(s_area) << "KoDirectoryStore::openReadOrWrite entering " << name.left(pos) << endl; + bool ret = enterDirectory( name.left( pos ) ); + popDirectory(); + if ( !ret ) + return false; + } + m_stream = new QFile( m_basePath + name ); + if ( !m_stream->open( iomode ) ) + { + delete m_stream; + m_stream = 0L; + return false; + } + if ( iomode == IO_ReadOnly ) + m_iSize = m_stream->size(); + return true; +} + +bool KoDirectoryStore::enterRelativeDirectory( const QString& dirName ) +{ + QDir origDir( m_currentPath ); + m_currentPath += dirName; + if ( !m_currentPath.endsWith("/") ) + m_currentPath += '/'; + //kdDebug(s_area) << "KoDirectoryStore::enterRelativeDirectory m_currentPath now " << m_currentPath << endl; + QDir newDir( m_currentPath ); + if ( newDir.exists() ) + return true; + // Dir doesn't exist. If reading -> error. If writing -> create. + if ( mode() == Write && origDir.mkdir( dirName ) ) { + kdDebug(s_area) << "Created " << dirName << " under " << origDir.absPath() << endl; + return true; + } + return false; +} + +bool KoDirectoryStore::enterAbsoluteDirectory( const QString& path ) +{ + m_currentPath = m_basePath + path; + //kdDebug(s_area) << "KoDirectoryStore::enterAbsoluteDirectory " << m_currentPath << endl; + QDir newDir( m_currentPath ); + Q_ASSERT( newDir.exists() ); // We've been there before, therefore it must exist. + return newDir.exists(); +} + +bool KoDirectoryStore::fileExists( const QString& absPath ) const +{ + kdDebug(s_area) << "KoDirectoryStore::fileExists " << m_basePath+absPath << endl; + return QFile::exists( m_basePath + absPath ); +} diff --git a/lib/store/KoDirectoryStore.h b/lib/store/KoDirectoryStore.h new file mode 100644 index 00000000..3efaa50b --- /dev/null +++ b/lib/store/KoDirectoryStore.h @@ -0,0 +1,54 @@ +/* This file is part of the KDE project + Copyright (C) 2002 David Faure <faure@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef koDirectoryStore_h +#define koDirectoryStore_h + +#include "KoStoreBase.h" + +class QFile; + +class KoDirectoryStore : public KoStoreBase +{ +public: + KoDirectoryStore( const QString& path, Mode _mode ); + ~KoDirectoryStore(); +protected: + virtual bool init( Mode _mode ); + virtual bool openWrite( const QString& name ) { return openReadOrWrite( name, IO_WriteOnly ); } + virtual bool openRead( const QString& name ) { return openReadOrWrite( name, IO_ReadOnly ); } + virtual bool closeRead() { return true; } + virtual bool closeWrite() { return true; } + virtual bool enterRelativeDirectory( const QString& dirName ); + virtual bool enterAbsoluteDirectory( const QString& path ); + virtual bool fileExists( const QString& absPath ) const; + + bool openReadOrWrite( const QString& name, int iomode ); +private: + // Path to base directory (== the ctor argument) + QString m_basePath; + + // Path to current directory + QString m_currentPath; + + // Current File + QFile* m_file; +}; + +#endif diff --git a/lib/store/KoStore.cpp b/lib/store/KoStore.cpp new file mode 100644 index 00000000..ec97c6a5 --- /dev/null +++ b/lib/store/KoStore.cpp @@ -0,0 +1,629 @@ +// -*- c-basic-offset: 2 -*- +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + Copyright (C) 2000-2002 David Faure <faure@kde.org>, Werner Trobin <trobin@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <stdio.h> +#include <assert.h> +#include <stdlib.h> + +#include "KoStore.h" +#include "KoTarStore.h" +#include "KoZipStore.h" +#include "KoDirectoryStore.h" + +#include <qfileinfo.h> +#include <qfile.h> +#include <qdir.h> + +#include <kurl.h> +#include <kdebug.h> +#include <kdeversion.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kio/netaccess.h> + +//#define DefaultFormat KoStore::Tar +#define DefaultFormat KoStore::Zip + +const int KoStore::s_area = 30002; + +KoStore::Backend KoStore::determineBackend( QIODevice* dev ) +{ + unsigned char buf[5]; + if ( dev->readBlock( (char *)buf, 4 ) < 4 ) + return DefaultFormat; // will create a "bad" store (bad()==true) + if ( buf[0] == 0037 && buf[1] == 0213 ) // gzip -> tar.gz + return Tar; + if ( buf[0] == 'P' && buf[1] == 'K' && buf[2] == 3 && buf[3] == 4 ) + return Zip; + return DefaultFormat; // fallback +} + +KoStore* KoStore::createStore( const QString& fileName, Mode mode, const QCString & appIdentification, Backend backend ) +{ + if ( backend == Auto ) { + if ( mode == KoStore::Write ) + backend = DefaultFormat; + else + { + QFileInfo inf( fileName ); + if ( inf.isDir() ) + backend = Directory; + else + { + QFile file( fileName ); + if ( file.open( IO_ReadOnly ) ) + backend = determineBackend( &file ); + else + backend = DefaultFormat; // will create a "bad" store (bad()==true) + } + } + } + switch ( backend ) + { + case Tar: + return new KoTarStore( fileName, mode, appIdentification ); + case Zip: + return new KoZipStore( fileName, mode, appIdentification ); + case Directory: + return new KoDirectoryStore( fileName /* should be a dir name.... */, mode ); + default: + kdWarning(s_area) << "Unsupported backend requested for KoStore : " << backend << endl; + return 0L; + } +} + +KoStore* KoStore::createStore( QIODevice *device, Mode mode, const QCString & appIdentification, Backend backend ) +{ + if ( backend == Auto ) + { + if ( mode == KoStore::Write ) + backend = DefaultFormat; + else { + if ( device->open( IO_ReadOnly ) ) { + backend = determineBackend( device ); + device->close(); + } + } + } + switch ( backend ) + { + case Tar: + return new KoTarStore( device, mode, appIdentification ); + case Directory: + kdError(s_area) << "Can't create a Directory store for a memory buffer!" << endl; + // fallback + case Zip: + return new KoZipStore( device, mode, appIdentification ); + default: + kdWarning(s_area) << "Unsupported backend requested for KoStore : " << backend << endl; + return 0L; + } +} + +KoStore* KoStore::createStore( QWidget* window, const KURL& url, Mode mode, const QCString & appIdentification, Backend backend ) +{ + if ( url.isLocalFile() ) + return createStore(url.path(), mode, appIdentification, backend ); + + QString tmpFile; + if ( mode == KoStore::Write ) + { + if ( backend == Auto ) + backend = DefaultFormat; + } + else + { + const bool downloaded = + KIO::NetAccess::download( url, tmpFile, window ); + + if (!downloaded) + { + kdError(s_area) << "Could not download file!" << endl; + backend = DefaultFormat; // will create a "bad" store (bad()==true) + } + else if ( backend == Auto ) + { + QFile file( tmpFile ); + if ( file.open( IO_ReadOnly ) ) + { + backend = determineBackend( &file ); + file.close(); + } + } + } + switch ( backend ) + { + case Tar: + return new KoTarStore( window, url, tmpFile, mode, appIdentification ); + case Zip: + return new KoZipStore( window, url, tmpFile, mode, appIdentification ); + default: + kdWarning(s_area) << "Unsupported backend requested for KoStore (KURL) : " << backend << endl; + KMessageBox::sorry( window, + i18n("The directory mode is not supported for remote locations."), + i18n("KOffice Storage")); + return 0L; + } +} + +namespace { + const char* const ROOTPART = "root"; + const char* const MAINNAME = "maindoc.xml"; +} + +bool KoStore::init( Mode _mode ) +{ + d = 0; + m_bIsOpen = false; + m_mode = _mode; + m_stream = 0; + + // Assume new style names. + m_namingVersion = NAMING_VERSION_2_2; + return true; +} + +KoStore::~KoStore() +{ + delete m_stream; +} + +bool KoStore::open( const QString & _name ) +{ + // This also converts from relative to absolute, i.e. merges the currentPath() + m_sName = toExternalNaming( _name ); + + if ( m_bIsOpen ) + { + kdWarning(s_area) << "KoStore: File is already opened" << endl; + //return KIO::ERR_INTERNAL; + return false; + } + + if ( m_sName.length() > 512 ) + { + kdError(s_area) << "KoStore: Filename " << m_sName << " is too long" << endl; + //return KIO::ERR_MALFORMED_URL; + return false; + } + + if ( m_mode == Write ) + { + kdDebug(s_area) << "KoStore: opening for writing '" << m_sName << "'" << endl; + if ( m_strFiles.findIndex( m_sName ) != -1 ) // just check if it's there + { + kdWarning(s_area) << "KoStore: Duplicate filename " << m_sName << endl; + //return KIO::ERR_FILE_ALREADY_EXIST; + return false; + } + + m_strFiles.append( m_sName ); + + m_iSize = 0; + if ( !openWrite( m_sName ) ) + return false; + } + else if ( m_mode == Read ) + { + kdDebug(s_area) << "Opening for reading '" << m_sName << "'" << endl; + if ( !openRead( m_sName ) ) + return false; + } + else + //return KIO::ERR_UNSUPPORTED_ACTION; + return false; + + m_bIsOpen = true; + return true; +} + +bool KoStore::isOpen() const +{ + return m_bIsOpen; +} + +bool KoStore::close() +{ + kdDebug(s_area) << "KoStore: Closing" << endl; + + if ( !m_bIsOpen ) + { + kdWarning(s_area) << "KoStore: You must open before closing" << endl; + //return KIO::ERR_INTERNAL; + return false; + } + + bool ret = m_mode == Write ? closeWrite() : closeRead(); + + delete m_stream; + m_stream = 0L; + m_bIsOpen = false; + return ret; +} + +QIODevice* KoStore::device() const +{ + if ( !m_bIsOpen ) + kdWarning(s_area) << "KoStore: You must open before asking for a device" << endl; + if ( m_mode != Read ) + kdWarning(s_area) << "KoStore: Can not get device from store that is opened for writing" << endl; + return m_stream; +} + +QByteArray KoStore::read( unsigned long int max ) +{ + QByteArray data; // Data is a QArray<char> + + if ( !m_bIsOpen ) + { + kdWarning(s_area) << "KoStore: You must open before reading" << endl; + data.resize( 0 ); + return data; + } + if ( m_mode != Read ) + { + kdError(s_area) << "KoStore: Can not read from store that is opened for writing" << endl; + data.resize( 0 ); + return data; + } + + if ( m_stream->atEnd() ) + { + data.resize( 0 ); + return data; + } + + if ( max > m_iSize - m_stream->at() ) + max = m_iSize - m_stream->at(); + if ( max == 0 ) + { + data.resize( 0 ); + return data; + } + + char *p = new char[ max ]; + m_stream->readBlock( p, max ); + + data.setRawData( p, max ); + return data; +} + +Q_LONG KoStore::write( const QByteArray& data ) +{ + return write( data.data(), data.size() ); // see below +} + +Q_LONG KoStore::read( char *_buffer, Q_ULONG _len ) +{ + if ( !m_bIsOpen ) + { + kdError(s_area) << "KoStore: You must open before reading" << endl; + return -1; + } + if ( m_mode != Read ) + { + kdError(s_area) << "KoStore: Can not read from store that is opened for writing" << endl; + return -1; + } + + if ( m_stream->atEnd() ) + return 0; + + if ( _len > m_iSize - m_stream->at() ) + _len = m_iSize - m_stream->at(); + if ( _len == 0 ) + return 0; + + return m_stream->readBlock( _buffer, _len ); +} + +Q_LONG KoStore::write( const char* _data, Q_ULONG _len ) +{ + if ( _len == 0L ) return 0; + + if ( !m_bIsOpen ) + { + kdError(s_area) << "KoStore: You must open before writing" << endl; + return 0L; + } + if ( m_mode != Write ) + { + kdError(s_area) << "KoStore: Can not write to store that is opened for reading" << endl; + return 0L; + } + + int nwritten = m_stream->writeBlock( _data, _len ); + Q_ASSERT( nwritten == (int)_len ); + m_iSize += nwritten; + + return nwritten; +} + +QIODevice::Offset KoStore::size() const +{ + if ( !m_bIsOpen ) + { + kdWarning(s_area) << "KoStore: You must open before asking for a size" << endl; + return static_cast<QIODevice::Offset>(-1); + } + if ( m_mode != Read ) + { + kdWarning(s_area) << "KoStore: Can not get size from store that is opened for writing" << endl; + return static_cast<QIODevice::Offset>(-1); + } + return m_iSize; +} + +bool KoStore::enterDirectory( const QString& directory ) +{ + //kdDebug(s_area) << "KoStore::enterDirectory " << directory << endl; + int pos; + bool success = true; + QString tmp( directory ); + + while ( ( pos = tmp.find( '/' ) ) != -1 && + ( success = enterDirectoryInternal( tmp.left( pos ) ) ) ) + tmp = tmp.mid( pos + 1 ); + + if ( success && !tmp.isEmpty() ) + return enterDirectoryInternal( tmp ); + return success; +} + +bool KoStore::leaveDirectory() +{ + if ( m_currentPath.isEmpty() ) + return false; + + m_currentPath.pop_back(); + + return enterAbsoluteDirectory( expandEncodedDirectory( currentPath() ) ); +} + +QString KoStore::currentDirectory() const +{ + return expandEncodedDirectory( currentPath() ); +} + +QString KoStore::currentPath() const +{ + QString path; + QStringList::ConstIterator it = m_currentPath.begin(); + QStringList::ConstIterator end = m_currentPath.end(); + for ( ; it != end; ++it ) { + path += *it; + path += '/'; + } + return path; +} + +void KoStore::pushDirectory() +{ + m_directoryStack.push( currentPath() ); +} + +void KoStore::popDirectory() +{ + m_currentPath.clear(); + enterAbsoluteDirectory( QString::null ); + enterDirectory( m_directoryStack.pop() ); +} + +bool KoStore::addLocalFile( const QString &fileName, const QString &destName ) +{ + QFileInfo fi( fileName ); + uint size = fi.size(); + QFile file( fileName ); + if ( !file.open( IO_ReadOnly )) + { + return false; + } + + if ( !open ( destName ) ) + { + return false; + } + + QByteArray data ( 8 * 1024 ); + + uint total = 0; + for ( int block = 0; ( block = file.readBlock ( data.data(), data.size() ) ) > 0; total += block ) + { + data.resize(block); + if ( write( data ) != block ) + return false; + data.resize(8*1024); + } + Q_ASSERT( total == size ); + + close(); + file.close(); + + return true; +} + +bool KoStore::extractFile ( const QString &srcName, const QString &fileName ) +{ + if ( !open ( srcName ) ) + return false; + + QFile file( fileName ); + + if( !file.open ( IO_WriteOnly ) ) + { + close(); + return false; + } + + QByteArray data ( 8 * 1024 ); + uint total = 0; + for( int block = 0; ( block = read ( data.data(), data.size() ) ) > 0; total += block ) + { + file.writeBlock ( data.data(), block ); + } + + if( size() != static_cast<QIODevice::Offset>(-1) ) + Q_ASSERT( total == size() ); + + file.close(); + close(); + + return true; +} + +QStringList KoStore::addLocalDirectory( const QString &dirPath, const QString &destName ) +{ + QString dot = "."; + QString dotdot = ".."; + QStringList content; + + QDir dir(dirPath); + if ( !dir.exists() ) + return 0; + + QStringList files = dir.entryList(); + for ( QStringList::Iterator it = files.begin(); it != files.end(); ++it ) + { + if ( *it != dot && *it != dotdot ) + { + QString currentFile = dirPath + "/" + *it; + QString dest = destName.isEmpty() ? *it : (destName + "/" + *it); + + QFileInfo fi ( currentFile ); + if ( fi.isFile() ) + { + addLocalFile ( currentFile, dest ); + content.append(dest); + } + else if ( fi.isDir() ) + { + content += addLocalDirectory ( currentFile, dest ); + } + } + } + + return content; +} + + +bool KoStore::at( QIODevice::Offset pos ) +{ + return m_stream->at( pos ); +} + +QIODevice::Offset KoStore::at() const +{ + return m_stream->at(); +} + +bool KoStore::atEnd() const +{ + return m_stream->atEnd(); +} + +// See the specification for details of what this function does. +QString KoStore::toExternalNaming( const QString & _internalNaming ) const +{ + if ( _internalNaming == ROOTPART ) + return expandEncodedDirectory( currentPath() ) + MAINNAME; + + QString intern; + if ( _internalNaming.startsWith( "tar:/" ) ) // absolute reference + intern = _internalNaming.mid( 5 ); // remove protocol + else + intern = currentPath() + _internalNaming; + + return expandEncodedPath( intern ); +} + +QString KoStore::expandEncodedPath( QString intern ) const +{ + if ( m_namingVersion == NAMING_VERSION_RAW ) + return intern; + + QString result; + int pos; + + if ( ( pos = intern.findRev( '/', -1 ) ) != -1 ) { + result = expandEncodedDirectory( intern.left( pos ) ) + '/'; + intern = intern.mid( pos + 1 ); + } + + // Now process the filename. If the first character is numeric, we have + // a main document. + if ( QChar(intern.at(0)).isDigit() ) + { + // If this is the first part name, check if we have a store with + // old-style names. + if ( ( m_namingVersion == NAMING_VERSION_2_2 ) && + ( m_mode == Read ) && + ( fileExists( result + "part" + intern + ".xml" ) ) ) + m_namingVersion = NAMING_VERSION_2_1; + + if ( m_namingVersion == NAMING_VERSION_2_1 ) + result = result + "part" + intern + ".xml"; + else + result = result + "part" + intern + "/" + MAINNAME; + } + else + result += intern; + return result; +} + +QString KoStore::expandEncodedDirectory( QString intern ) const +{ + if ( m_namingVersion == NAMING_VERSION_RAW ) + return intern; + + QString result; + int pos; + while ( ( pos = intern.find( '/' ) ) != -1 ) { + if ( QChar(intern.at(0)).isDigit() ) + result += "part"; + result += intern.left( pos + 1 ); // copy numbers (or "pictures") + "/" + intern = intern.mid( pos + 1 ); // remove the dir we just processed + } + + if ( QChar(intern.at(0)).isDigit() ) + result += "part"; + result += intern; + return result; +} + +bool KoStore::enterDirectoryInternal( const QString& directory ) +{ + if ( enterRelativeDirectory( expandEncodedDirectory( directory ) ) ) + { + m_currentPath.append( directory ); + return true; + } + return false; +} + +void KoStore::disallowNameExpansion( void ) +{ + m_namingVersion = NAMING_VERSION_RAW; +} + +bool KoStore::hasFile( const QString& fileName ) const +{ + return fileExists( toExternalNaming( currentPath() + fileName ) ); +} diff --git a/lib/store/KoStore.h b/lib/store/KoStore.h new file mode 100644 index 00000000..38409688 --- /dev/null +++ b/lib/store/KoStore.h @@ -0,0 +1,381 @@ +// -*- c-basic-offset: 2 -*- +/* This file is part of the KDE project + Copyright (C) 1998, 1999 David Faure <faure@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef __koStore_h_ +#define __koStore_h_ + +#include <qstring.h> +#include <qstringlist.h> +#include <qiodevice.h> +#include <qvaluestack.h> +#include <koffice_export.h> + +class QWidget; + +class KURL; + +/** + * Saves and loads KOffice documents using various backends. Currently supported + * backends are ZIP, tar and directory. + * We call a "store" the file on the hard disk (the one the users sees) + * and call a "file" a file inside the store. + */ +class KOSTORE_EXPORT KoStore +{ +public: + + enum Mode { Read, Write }; + enum Backend { Auto, Tar, Zip, Directory }; + + /** + * Open a store (i.e. the representation on disk of a KOffice document). + * + * @param fileName the name of the file to open + * @param mode if KoStore::Read, open an existing store to read it. + * if KoStore::Write, create or replace a store. + * @param backend the backend to use for the data storage. + * Auto means automatically-determined for reading, + * and the current format (now Zip) for writing. + * + * @param appIdentification the application's mimetype, + * to be written in the file for "mime-magic" identification. + * Only meaningful if mode is Write, and if backend!=Directory. + */ + static KoStore* createStore( const QString& fileName, Mode mode, const QCString & appIdentification = "", Backend backend = Auto ); + + /** + * Create a store for any kind of QIODevice: file, memory buffer... + * KoStore will take care of opening the QIODevice. + * This method doesn't support the Directory store! + */ + static KoStore* createStore( QIODevice *device, Mode mode, const QCString & appIdentification = "", Backend backend = Auto ); + + /** + * Open a store (i.e. the representation on disk of a KOffice document). + * + * @param window associated window (for the progress bar dialog and authentification) + * @param url URL of the file to open + * @param mode if KoStore::Read, open an existing store to read it. + * if KoStore::Write, create or replace a store. + * @param backend the backend to use for the data storage. + * Auto means automatically-determined for reading, + * and the current format (now Zip) for writing. + * + * @param appIdentification the application's mimetype, + * to be written in the file for "mime-magic" identification. + * Only meaningful if mode is Write, and if backend!=Directory. + * + * If the file is remote, the backend Directory cannot be used! + * + * @since 1.4 + * @bug saving not completely implemented (fixed temporary file) + */ + static KoStore* createStore( QWidget* window, const KURL& url, Mode mode, const QCString & appIdentification = "", Backend backend = Auto ); + + /** + * Destroys the store (i.e. closes the file on the hard disk) + */ + virtual ~KoStore(); + + /** + * Open a new file inside the store + * @param name The filename, internal representation ("root", "tar:/0"... ). + * If the tar:/ prefix is missing it's assumed to be a relative URI. + * @return true on success. + */ + bool open( const QString & name ); + + /** + * Check whether a file inside the store is currently opened with open(), + * ready to be read or written. + * @return true if a file is currently opened. + */ + bool isOpen() const; + + /** + * Close the file inside the store + * @return true on success. + */ + bool close(); + + /** + * Get a device for reading a file from the store directly + * (slightly faster than read() calls) + * You need to call @ref open first, and @ref close afterwards. + */ + QIODevice* device() const; + + /** + * Read data from the currently opened file. You can also use the streams + * for this. + */ + QByteArray read( unsigned long int max ); + + /** + * Write data into the currently opened file. You can also use the streams + * for this. + */ + Q_LONG write( const QByteArray& _data ); + + /** + * Read data from the currently opened file. You can also use the streams + * for this. + * @return size of data read, -1 on error + */ + Q_LONG read( char *_buffer, Q_ULONG _len ); + + /** + * Write data into the currently opened file. You can also use the streams + * for this. + */ + virtual Q_LONG write( const char* _data, Q_ULONG _len ); + + /** + * @return the size of the currently opened file, -1 on error. + * Can be used as an argument for the read methods, for instance + */ + QIODevice::Offset size() const; + + /** + * @return true if an error occurred + */ + bool bad() const { return !m_bGood; } // :) + + /** + * @return the mode used when opening, read or write + */ + Mode mode() const { return m_mode; } + + /** + * Enters one or multiple directories. In Read mode this actually + * checks whether the specified directories exist and returns false + * if they don't. In Write mode we don't create the directory, we + * just use the "current directory" to generate the absolute path + * if you pass a relative path (one not starting with tar:/) when + * opening a stream. + * Note: Operates on internal names + */ + bool enterDirectory( const QString& directory ); + + /** + * Leaves a directory. Equivalent to "cd .." + * @return true on success, false if we were at the root already to + * make it possible to "loop to the root" + */ + bool leaveDirectory(); + + /** + * Returns the current path including a trailing slash. + * Note: Returns a path in "internal name" style + */ + QString currentPath() const; + + /** + * Returns the current directory. + * Note: Returns a path in "internal name" style + */ + QString currentDirectory() const; + + + /** + * Stacks the current directory. Restore the current path using + * @ref popDirectory . + */ + void pushDirectory(); + + /** + * Restores the previously pushed directory. No-op if the stack is + * empty. + */ + void popDirectory(); + + /** + * @return true if the given file exists in the current directory, + * i.e. if open(fileName) will work. + */ + bool hasFile( const QString& fileName ) const; + + /** + * Imports a local file into a store + * @param fileName file on hard disk + * @param destName file in the store + */ + bool addLocalFile( const QString &fileName, const QString &destName ); + + /** + * Imports a local directory + * @param dirPath path to the directory on a disk + * @param dest path in the store where the directory should get saved + * @return the directory index + */ + QStringList addLocalDirectory( const QString &dirPath, const QString &dest ); + + + /** + * Extracts a file out of the store + * @param srcName file in the store + * @param fileName file on a disk + */ + bool extractFile( const QString &srcName, const QString &fileName ); + + //@{ + /// See QIODevice + bool at( QIODevice::Offset pos ); + QIODevice::Offset at() const; + bool atEnd() const; + //@} + + /** + * Do not expand file and directory names + * Useful when using KoStore on non-KOffice files. + * (This method should be called just after the constructor) + */ + void disallowNameExpansion( void ); + +protected: + + KoStore() {} + + /** + * Init store - called by constructor. + * @return true on success + */ + virtual bool init( Mode mode ); + /** + * Open the file @p name in the store, for writing + * On success, this method must set m_stream to a stream in which we can write. + * @param name "absolute path" (in the archive) to the file to open + * @return true on success + */ + virtual bool openWrite( const QString& name ) = 0; + /** + * Open the file @p name in the store, for reading. + * On success, this method must set m_stream to a stream from which we can read, + * as well as setting m_iSize to the size of the file. + * @param name "absolute path" (in the archive) to the file to open + * @return true on success + */ + virtual bool openRead( const QString& name ) = 0; + + /** + * @return true on success + */ + virtual bool closeRead() = 0; + /** + * @return true on success + */ + virtual bool closeWrite() = 0; + + /** + * Enter a subdirectory of the current directory. + * The directory might not exist yet in Write mode. + */ + virtual bool enterRelativeDirectory( const QString& dirName ) = 0; + /** + * Enter a directory where we've been before. + * It is guaranteed to always exist. + */ + virtual bool enterAbsoluteDirectory( const QString& path ) = 0; + + /** + * Check if a file exists inside the store. + * @param absPath the absolute path inside the store, i.e. not relative to the current directory + */ + virtual bool fileExists( const QString& absPath ) const = 0; + +private: + static Backend determineBackend( QIODevice* dev ); + + /** + * Conversion routine + * @param _internalNaming name used internally : "root", "tar:/0", ... + * @return the name used in the file, more user-friendly ("maindoc.xml", + * "part0/maindoc.xml", ...) + * Examples: + * + * tar:/0 is saved as part0/maindoc.xml + * tar:/0/1 is saved as part0/part1/maindoc.xml + * tar:/0/1/pictures/picture0.png is saved as part0/part1/pictures/picture0.png + * + * see specification (koffice/lib/store/SPEC) for details. + */ + QString toExternalNaming( const QString & _internalNaming ) const; + + /** + * Expands a full path name for a stream (directories+filename) + */ + QString expandEncodedPath( QString intern ) const; + + /** + * Expands only directory names(!) + * Needed for the path handling code, as we only operate on internal names + */ + QString expandEncodedDirectory( QString intern ) const; + + mutable enum + { + NAMING_VERSION_2_1, + NAMING_VERSION_2_2, + NAMING_VERSION_RAW ///< Never expand file and directory names + } m_namingVersion; + + /** + * Enter *one* single directory. Nothing like foo/bar/bleh allowed. + * Performs some checking when in Read mode + */ + bool enterDirectoryInternal( const QString& directory ); + +protected: + + Mode m_mode; + + /// Store the filenames (with full path inside the archive) when writing, to avoid duplicates + QStringList m_strFiles; + + /// The "current directory" (path) + QStringList m_currentPath; + + /// Used to push/pop directories to make it easy to save/restore the state + QValueStack<QString> m_directoryStack; + + /// Current filename (between an open() and a close()) + QString m_sName; + /// Current size of the file named m_sName + QIODevice::Offset m_iSize; + + /// The stream for the current read or write operation + QIODevice * m_stream; + + bool m_bIsOpen; + /// Must be set by the constructor. + bool m_bGood; + + static const int s_area; + +private: + KoStore( const KoStore& store ); ///< don't copy + KoStore& operator=( const KoStore& store ); ///< don't assign + + class Private; + Private * d; + +}; + +#endif diff --git a/lib/store/KoStoreBase.cpp b/lib/store/KoStoreBase.cpp new file mode 100644 index 00000000..28280a40 --- /dev/null +++ b/lib/store/KoStoreBase.cpp @@ -0,0 +1,29 @@ +// +/* This file is part of the KDE project + Copyright 2004 Nicolas GOUTTE <goutte@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "KoStoreBase.h" + +KoStoreBase::KoStoreBase(void) : m_fileMode(Local), m_window(0) +{ +} + +KoStoreBase::~KoStoreBase(void) +{ +} diff --git a/lib/store/KoStoreBase.h b/lib/store/KoStoreBase.h new file mode 100644 index 00000000..bd4be8ac --- /dev/null +++ b/lib/store/KoStoreBase.h @@ -0,0 +1,51 @@ +// +/* This file is part of the KDE project + Copyright 2004 Nicolas GOUTTE <goutte@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KOSTORE_BASE_H +#define KOSTORE_BASE_H + +#include <kurl.h> + +#include "KoStore.h" + +/** + * Helper class for KoStore (mainly for remote file support) + * @since 1.4 + */ +class KoStoreBase : public KoStore +{ +public: + KoStoreBase(void); + virtual ~KoStoreBase(void); +public: + enum FileMode { /*Bad=0,*/ Local=1, RemoteRead, RemoteWrite }; + +protected: + /** + * original URL of the remote file + * (undefined for a local file) + */ + KURL m_url; + FileMode m_fileMode; + QString m_localFileName; + QWidget* m_window; +}; + +#endif //KOSTORE_BASE_H diff --git a/lib/store/KoStoreDevice.h b/lib/store/KoStoreDevice.h new file mode 100644 index 00000000..e76013bd --- /dev/null +++ b/lib/store/KoStoreDevice.h @@ -0,0 +1,88 @@ +/* This file is part of the KDE project + Copyright (C) 2000 David Faure <faure@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef koStoreDevice_h +#define koStoreDevice_h + +#include <KoStore.h> + +/** + * This class implements a QIODevice around KoStore, so that + * it can be used to create a QDomDocument from it, to be written or read + * using QDataStream or to be written using QTextStream + */ +class KoStoreDevice : public QIODevice +{ +public: + /// Note: KoStore::open() should be called before calling this. + KoStoreDevice( KoStore * store ) : m_store(store) { + setType( IO_Direct ); + } + ~KoStoreDevice() {} + + bool open( int m ) { + if ( m & IO_ReadOnly ) + return ( m_store->mode() == KoStore::Read ); + if ( m & IO_WriteOnly ) + return ( m_store->mode() == KoStore::Write ); + return false; + } + void close() { } + void flush() { } + + Offset size() const { + if ( m_store->mode() == KoStore::Read ) + return m_store->size(); + else + return 0xffffffff; + } + + virtual Q_LONG readBlock( char *data, Q_ULONG maxlen ) { return m_store->read(data, maxlen); } + virtual Q_LONG writeBlock( const char *data, Q_ULONG len ) { return m_store->write( data, len ); } + // Not virtual, only to uncover shadow + Q_LONG writeBlock( const QByteArray& data ) { return QIODevice::writeBlock( data ); } + + int getch() { + char c[2]; + if ( m_store->read(c, 1) == -1) + return -1; + else + return c[0]; + } + int putch( int _c ) { + char c[2]; + c[0] = _c; + c[1] = 0; + if (m_store->write( c, 1 ) == 1) + return _c; + else + return -1; + } + int ungetch( int ) { return -1; } // unsupported + + // See QIODevice + virtual bool at( Offset pos ) { return m_store->at(pos); } + virtual Offset at() const { return m_store->at(); } + virtual bool atEnd() const { return m_store->atEnd(); } + +protected: + KoStore * m_store; +}; + +#endif diff --git a/lib/store/KoStoreDrag.cpp b/lib/store/KoStoreDrag.cpp new file mode 100644 index 00000000..ea951f53 --- /dev/null +++ b/lib/store/KoStoreDrag.cpp @@ -0,0 +1,35 @@ +/* This file is part of the KDE project + Copyright (C) 2002 David Faure <faure@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "KoStoreDrag.h" + +QCString KoStoreDrag::mimeType( const char* nativeMimeType ) +{ + return QCString(nativeMimeType); // + "-selection"; removed for OASIS +} + +KoStoreDrag::KoStoreDrag( const char* nativeMimeType, QWidget *dragSource, const char *name ) + : QStoredDrag( mimeType(nativeMimeType), dragSource, name ) +{ +} + +bool KoStoreDrag::canDecode( const char* nativeMimeType, QMimeSource* e ) +{ + return e->provides( mimeType(nativeMimeType) ); +} diff --git a/lib/store/KoStoreDrag.h b/lib/store/KoStoreDrag.h new file mode 100644 index 00000000..0e3939d4 --- /dev/null +++ b/lib/store/KoStoreDrag.h @@ -0,0 +1,55 @@ +/* This file is part of the KDE project + Copyright (C) 2002 David Faure <faure@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef koStoreDrag_h +#define koStoreDrag_h + +#include <qdragobject.h> +#include <koffice_export.h> +/** + * A generic drag object that holds a store (e.g. KoZipStore) in memory. + * This allows to drag-n-drop and copy-paste complex koffice objects. + * As per usual with dragobjects, an instance of KoStoreDrag must be + * created on the "sending" side (dragging or copying). The "receiving" + * side (dropping or pasting) only uses provides()/canDecode() and encodedData(). + * + * To create the data in memory, create a QBuffer, + * then KoStore::createStore( theBuffer, .... ), save the + * data into the store and delete it. Finally, call setEncodedData(). + */ +class KOSTORE_EXPORT KoStoreDrag : public QStoredDrag +{ +public: + /** Constructor. + * @param nativeMimeType the app's native mimetype. + * @param dragSource must be 0 when copying to the clipboard. + * @param name object name for this drag. + */ + KoStoreDrag( const char* nativeMimeType, QWidget *dragSource = 0L, const char *name = 0L ); + + static bool canDecode( const char* nativeMimeType, QMimeSource* e ); + + /** + * Returns the mimetype of the clipboard data for a given application, + * depending on the application's native mimetype. + */ + static QCString mimeType( const char* nativeMimeType ); +}; + +#endif diff --git a/lib/store/KoTarStore.cpp b/lib/store/KoTarStore.cpp new file mode 100644 index 00000000..fc516894 --- /dev/null +++ b/lib/store/KoTarStore.cpp @@ -0,0 +1,206 @@ +/* This file is part of the KDE project + Copyright (C) 2000-2002 David Faure <faure@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "KoTarStore.h" + +#include <qbuffer.h> + +#include <ktar.h> +#include <kdebug.h> +#include <kurl.h> +#include <kdeversion.h> +#include <kio/netaccess.h> + +KoTarStore::KoTarStore( const QString & _filename, Mode _mode, const QCString & appIdentification ) +{ + kdDebug(s_area) << "KoTarStore Constructor filename = " << _filename + << " mode = " << int(_mode) << endl; + + m_pTar = new KTar( _filename, "application/x-gzip" ); + + m_bGood = init( _mode ); // open the targz file and init some vars + kdDebug()<<"appIdentification :"<<appIdentification<<endl; + if ( m_bGood && _mode == Write ) + m_pTar->setOrigFileName( completeMagic( appIdentification ) ); +} + +KoTarStore::KoTarStore( QIODevice *dev, Mode mode, const QCString & appIdentification ) +{ + m_pTar = new KTar( dev ); + + m_bGood = init( mode ); + + if ( m_bGood && mode == Write ) + m_pTar->setOrigFileName( completeMagic( appIdentification ) ); +} + +KoTarStore::KoTarStore( QWidget* window, const KURL& _url, const QString & _filename, Mode _mode, const QCString & appIdentification ) +{ + kdDebug(s_area) << "KoTarStore Constructor url= " << _url.prettyURL() + << " filename = " << _filename + << " mode = " << int(_mode) << endl; + + m_url = _url; + m_window = window; + + if ( _mode == KoStore::Read ) + { + m_fileMode = KoStoreBase::RemoteRead; + m_localFileName = _filename; + + } + else + { + m_fileMode = KoStoreBase::RemoteWrite; + m_localFileName = "/tmp/kozip"; // ### FIXME with KTempFile + } + + m_pTar = new KTar( m_localFileName, "application/x-gzip" ); + + m_bGood = init( _mode ); // open the targz file and init some vars + + if ( m_bGood && _mode == Write ) + m_pTar->setOrigFileName( completeMagic( appIdentification ) ); +} + +KoTarStore::~KoTarStore() +{ + m_pTar->close(); + delete m_pTar; + + // Now we have still some job to do for remote files. + if ( m_fileMode == KoStoreBase::RemoteRead ) + { + KIO::NetAccess::removeTempFile( m_localFileName ); + } + else if ( m_fileMode == KoStoreBase::RemoteWrite ) + { + KIO::NetAccess::upload( m_localFileName, m_url, m_window ); + // ### FIXME: delete temp file + } +} + +QCString KoTarStore::completeMagic( const QCString& appMimetype ) +{ + kdDebug()<<"QCString KoTarStore::completeMagic( const QCString& appMimetype )********************\n"; + QCString res( "KOffice " ); + res += appMimetype; + res += '\004'; // Two magic bytes to make the identification + res += '\006'; // more reliable (DF) + kdDebug()<<"sssssssssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"; + kdDebug()<<" return :!!!!!!!!!!!!!!! :"<<res<<endl; + return res; +} + +bool KoTarStore::init( Mode _mode ) +{ + KoStore::init( _mode ); + m_currentDir = 0; + bool good = m_pTar->open( _mode == Write ? IO_WriteOnly : IO_ReadOnly ); + + if ( good && _mode == Read ) + good = m_pTar->directory() != 0; + return good; +} + +// When reading, m_stream comes directly from KArchiveFile::device() +// When writing, m_stream buffers the data into m_byteArray + +bool KoTarStore::openWrite( const QString& /*name*/ ) +{ + // Prepare memory buffer for writing + m_byteArray.resize( 0 ); + m_stream = new QBuffer( m_byteArray ); + m_stream->open( IO_WriteOnly ); + return true; +} + +bool KoTarStore::openRead( const QString& name ) +{ + const KTarEntry * entry = m_pTar->directory()->entry( name ); + if ( entry == 0L ) + { + //kdWarning(s_area) << "Unknown filename " << name << endl; + //return KIO::ERR_DOES_NOT_EXIST; + return false; + } + if ( entry->isDirectory() ) + { + kdWarning(s_area) << name << " is a directory !" << endl; + //return KIO::ERR_IS_DIRECTORY; + return false; + } + KTarFile * f = (KTarFile *) entry; + m_byteArray.resize( 0 ); + delete m_stream; + m_stream = f->device(); + m_iSize = f->size(); + return true; +} + +bool KoTarStore::closeWrite() +{ + // write the whole bytearray at once into the tar file + + kdDebug(s_area) << "Writing file " << m_sName << " into TAR archive. size " + << m_iSize << endl; + if ( !m_pTar->writeFile( m_sName , "user", "group", m_iSize, m_byteArray.data() ) ) + kdWarning( s_area ) << "Failed to write " << m_sName << endl; + m_byteArray.resize( 0 ); // save memory + return true; +} + +bool KoTarStore::enterRelativeDirectory( const QString& dirName ) +{ + if ( m_mode == Read ) { + if ( !m_currentDir ) { + m_currentDir = m_pTar->directory(); // initialize + Q_ASSERT( m_currentPath.isEmpty() ); + } + const KArchiveEntry *entry = m_currentDir->entry( dirName ); + if ( entry && entry->isDirectory() ) { + m_currentDir = dynamic_cast<const KArchiveDirectory*>( entry ); + return m_currentDir != 0; + } + return false; + } + else // Write, no checking here + return true; +} + +bool KoTarStore::enterAbsoluteDirectory( const QString& path ) +{ + if ( path.isEmpty() ) + { + m_currentDir = 0; + return true; + } + if ( m_mode == Read ) { + m_currentDir = dynamic_cast<const KArchiveDirectory*>( m_pTar->directory()->entry( path ) ); + Q_ASSERT( m_currentDir ); + return m_currentDir != 0; + } + else + return true; +} + +bool KoTarStore::fileExists( const QString& absPath ) const +{ + return m_pTar->directory()->entry( absPath ) != 0; +} diff --git a/lib/store/KoTarStore.h b/lib/store/KoTarStore.h new file mode 100644 index 00000000..21ab3ac6 --- /dev/null +++ b/lib/store/KoTarStore.h @@ -0,0 +1,65 @@ +/* This file is part of the KDE project + Copyright (C) 2002 David Faure <faure@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef koTarStore_h +#define koTarStore_h + +#include "KoStoreBase.h" + +class KTar; +class KArchiveDirectory; +class KURL; + +class KoTarStore : public KoStoreBase +{ +public: + KoTarStore( const QString & _filename, Mode _mode, const QCString & appIdentification ); + KoTarStore( QIODevice *dev, Mode mode, const QCString & appIdentification ); + /** + * KURL-constructor + * @todo saving not completely implemented (fixed temporary file) + * @since 1.4 + */ + KoTarStore( QWidget* window, const KURL& url, const QString & _filename, Mode _mode, const QCString & appIdentification ); + ~KoTarStore(); +protected: + virtual bool init( Mode _mode ); + virtual bool openWrite( const QString& name ); + virtual bool openRead( const QString& name ); + virtual bool closeWrite(); + virtual bool closeRead() { return true; } + virtual bool enterRelativeDirectory( const QString& dirName ); + virtual bool enterAbsoluteDirectory( const QString& path ); + virtual bool fileExists( const QString& absPath ) const; + + static QCString completeMagic( const QCString& appMimetype ); + + /// The tar archive + KTar * m_pTar; + + /** In "Read" mode this pointer is pointing to the + current directory in the archive to speed up the verification process */ + const KArchiveDirectory* m_currentDir; + + /// Buffer used when writing + QByteArray m_byteArray; + +}; + +#endif diff --git a/lib/store/KoXmlWriter.cpp b/lib/store/KoXmlWriter.cpp new file mode 100644 index 00000000..2670e304 --- /dev/null +++ b/lib/store/KoXmlWriter.cpp @@ -0,0 +1,427 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <faure@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "KoXmlWriter.h" + +#include <kglobal.h> // kMin +#include <kdebug.h> +#include <qiodevice.h> +#include <float.h> + +static const int s_indentBufferLength = 100; + +KoXmlWriter::KoXmlWriter( QIODevice* dev, int indentLevel ) + : m_dev( dev ), m_baseIndentLevel( indentLevel ) +{ + init(); +} + +void KoXmlWriter::init() +{ + m_indentBuffer = new char[ s_indentBufferLength ]; + memset( m_indentBuffer, ' ', s_indentBufferLength ); + *m_indentBuffer = '\n'; // write newline before indentation, in one go + + m_escapeBuffer = new char[s_escapeBufferLen]; +} + +KoXmlWriter::~KoXmlWriter() +{ + delete[] m_indentBuffer; + delete[] m_escapeBuffer; +} + +void KoXmlWriter::startDocument( const char* rootElemName, const char* publicId, const char* systemId ) +{ + Q_ASSERT( m_tags.isEmpty() ); + writeCString( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ); + // There isn't much point in a doctype if there's no DTD to refer to + // (I'm told that files that are validated by a RelaxNG schema cannot refer to the schema) + if ( publicId ) { + writeCString( "<!DOCTYPE " ); + writeCString( rootElemName ); + writeCString( " PUBLIC \"" ); + writeCString( publicId ); + writeCString( "\" \"" ); + writeCString( systemId ); + writeCString( "\"" ); + writeCString( ">\n" ); + } +} + +void KoXmlWriter::endDocument() +{ + // just to do exactly like QDom does (newline at end of file). + writeChar( '\n' ); + Q_ASSERT( m_tags.isEmpty() ); +} + +// returns the value of indentInside of the parent +bool KoXmlWriter::prepareForChild() +{ + if ( !m_tags.isEmpty() ) { + Tag& parent = m_tags.top(); + if ( !parent.hasChildren ) { + closeStartElement( parent ); + parent.hasChildren = true; + parent.lastChildIsText = false; + } + if ( parent.indentInside ) { + writeIndent(); + } + return parent.indentInside; + } + return true; +} + +void KoXmlWriter::prepareForTextNode() +{ + Tag& parent = m_tags.top(); + if ( !parent.hasChildren ) { + closeStartElement( parent ); + parent.hasChildren = true; + parent.lastChildIsText = true; + } +} + +void KoXmlWriter::startElement( const char* tagName, bool indentInside ) +{ + Q_ASSERT( tagName != 0 ); + + // Tell parent that it has children + bool parentIndent = prepareForChild(); + + m_tags.push( Tag( tagName, parentIndent && indentInside ) ); + writeChar( '<' ); + writeCString( tagName ); + //kdDebug() << k_funcinfo << tagName << endl; +} + +void KoXmlWriter::addCompleteElement( const char* cstr ) +{ + prepareForChild(); + writeCString( cstr ); +} + + +void KoXmlWriter::addCompleteElement( QIODevice* indev ) +{ + prepareForChild(); + bool openOk = indev->open( IO_ReadOnly ); + Q_ASSERT( openOk ); + if ( !openOk ) + return; + static const int MAX_CHUNK_SIZE = 8*1024; // 8 KB + QByteArray buffer(MAX_CHUNK_SIZE); + while ( !indev->atEnd() ) { + Q_LONG len = indev->readBlock( buffer.data(), buffer.size() ); + if ( len <= 0 ) // e.g. on error + break; + m_dev->writeBlock( buffer.data(), len ); + } +} + +void KoXmlWriter::endElement() +{ + if ( m_tags.isEmpty() ) + kdWarning() << "Ouch, endElement() was called more times than startElement(). " + "The generated XML will be invalid! " + "Please report this bug (by saving the document to another format...)" << endl; + + Tag tag = m_tags.pop(); + //kdDebug() << k_funcinfo << " tagName=" << tag.tagName << " hasChildren=" << tag.hasChildren << endl; + if ( !tag.hasChildren ) { + writeCString( "/>" ); + } + else { + if ( tag.indentInside && !tag.lastChildIsText ) { + writeIndent(); + } + writeCString( "</" ); + Q_ASSERT( tag.tagName != 0 ); + writeCString( tag.tagName ); + writeChar( '>' ); + } +} + +void KoXmlWriter::addTextNode( const char* cstr ) +{ + prepareForTextNode(); + char* escaped = escapeForXML( cstr, -1 ); + writeCString( escaped ); + if(escaped != m_escapeBuffer) + delete[] escaped; +} + +void KoXmlWriter::addProcessingInstruction( const char* cstr ) +{ + prepareForTextNode(); + writeCString( "<?" ); + addTextNode( cstr ); + writeCString( "?>"); +} + +void KoXmlWriter::addAttribute( const char* attrName, const char* value ) +{ + writeChar( ' ' ); + writeCString( attrName ); + writeCString("=\""); + char* escaped = escapeForXML( value, -1 ); + writeCString( escaped ); + if(escaped != m_escapeBuffer) + delete[] escaped; + writeChar( '"' ); +} + +void KoXmlWriter::addAttribute( const char* attrName, double value ) +{ + QCString str; + str.setNum( value, 'g', DBL_DIG ); + addAttribute( attrName, str.data() ); +} + +void KoXmlWriter::addAttributePt( const char* attrName, double value ) +{ + QCString str; + str.setNum( value, 'g', DBL_DIG ); + str += "pt"; + addAttribute( attrName, str.data() ); +} + +void KoXmlWriter::writeIndent() +{ + // +1 because of the leading '\n' + m_dev->writeBlock( m_indentBuffer, kMin( indentLevel() + 1, + s_indentBufferLength ) ); +} + +void KoXmlWriter::writeString( const QString& str ) +{ + // cachegrind says .utf8() is where most of the time is spent + QCString cstr = str.utf8(); + m_dev->writeBlock( cstr.data(), cstr.size() - 1 ); +} + +// In case of a reallocation (ret value != m_buffer), the caller owns the return value, +// it must delete it (with []) +char* KoXmlWriter::escapeForXML( const char* source, int length = -1 ) const +{ + // we're going to be pessimistic on char length; so lets make the outputLength less + // the amount one char can take: 6 + char* destBoundary = m_escapeBuffer + s_escapeBufferLen - 6; + char* destination = m_escapeBuffer; + char* output = m_escapeBuffer; + const char* src = source; // src moves, source remains + for ( ;; ) { + if(destination >= destBoundary) { + // When we come to realize that our escaped string is going to + // be bigger than the escape buffer (this shouldn't happen very often...), + // we drop the idea of using it, and we allocate a bigger buffer. + // Note that this if() can only be hit once per call to the method. + if ( length == -1 ) + length = qstrlen( source ); // expensive... + uint newLength = length * 6 + 1; // worst case. 6 is due to " and ' + char* buffer = new char[ newLength ]; + destBoundary = buffer + newLength; + uint amountOfCharsAlreadyCopied = destination - m_escapeBuffer; + memcpy( buffer, m_escapeBuffer, amountOfCharsAlreadyCopied ); + output = buffer; + destination = buffer + amountOfCharsAlreadyCopied; + } + switch( *src ) { + case 60: // < + memcpy( destination, "<", 4 ); + destination += 4; + break; + case 62: // > + memcpy( destination, ">", 4 ); + destination += 4; + break; + case 34: // " + memcpy( destination, """, 6 ); + destination += 6; + break; +#if 0 // needed? + case 39: // ' + memcpy( destination, "'", 6 ); + destination += 6; + break; +#endif + case 38: // & + memcpy( destination, "&", 5 ); + destination += 5; + break; + case 0: + *destination = '\0'; + return output; + default: + *destination++ = *src++; + continue; + } + ++src; + } + // NOTREACHED (see case 0) + return output; +} + +void KoXmlWriter::addManifestEntry( const QString& fullPath, const QString& mediaType ) +{ + startElement( "manifest:file-entry" ); + addAttribute( "manifest:media-type", mediaType ); + addAttribute( "manifest:full-path", fullPath ); + endElement(); +} + +void KoXmlWriter::addConfigItem( const QString & configName, const QString& value ) +{ + startElement( "config:config-item" ); + addAttribute( "config:name", configName ); + addAttribute( "config:type", "string" ); + addTextNode( value ); + endElement(); +} + +void KoXmlWriter::addConfigItem( const QString & configName, bool value ) +{ + startElement( "config:config-item" ); + addAttribute( "config:name", configName ); + addAttribute( "config:type", "boolean" ); + addTextNode( value ? "true" : "false" ); + endElement(); +} + +void KoXmlWriter::addConfigItem( const QString & configName, int value ) +{ + startElement( "config:config-item" ); + addAttribute( "config:name", configName ); + addAttribute( "config:type", "int"); + addTextNode(QString::number( value ) ); + endElement(); +} + +void KoXmlWriter::addConfigItem( const QString & configName, double value ) +{ + startElement( "config:config-item" ); + addAttribute( "config:name", configName ); + addAttribute( "config:type", "double" ); + addTextNode( QString::number( value ) ); + endElement(); +} + +void KoXmlWriter::addConfigItem( const QString & configName, long value ) +{ + startElement( "config:config-item" ); + addAttribute( "config:name", configName ); + addAttribute( "config:type", "long" ); + addTextNode( QString::number( value ) ); + endElement(); +} + +void KoXmlWriter::addConfigItem( const QString & configName, short value ) +{ + startElement( "config:config-item" ); + addAttribute( "config:name", configName ); + addAttribute( "config:type", "short" ); + addTextNode( QString::number( value ) ); + endElement(); +} + +void KoXmlWriter::addTextSpan( const QString& text ) +{ + QMap<int, int> tabCache; + addTextSpan( text, tabCache ); +} + +void KoXmlWriter::addTextSpan( const QString& text, const QMap<int, int>& tabCache ) +{ + uint len = text.length(); + int nrSpaces = 0; // number of consecutive spaces + bool leadingSpace = false; + QString str; + str.reserve( len ); + + // Accumulate chars either in str or in nrSpaces (for spaces). + // Flush str when writing a subelement (for spaces or for another reason) + // Flush nrSpaces when encountering two or more consecutive spaces + for ( uint i = 0; i < len ; ++i ) { + QChar ch = text[i]; + if ( ch != ' ' ) { + if ( nrSpaces > 0 ) { + // For the first space we use ' '. + // "it is good practice to use (text:s) for the second and all following SPACE + // characters in a sequence." (per the ODF spec) + // however, per the HTML spec, "authors should not rely on user agents to render + // white space immediately after a start tag or immediately before an end tag" + // (and both we and OO.o ignore leading spaces in <text:p> or <text:h> elements...) + if (!leadingSpace) + { + str += ' '; + --nrSpaces; + } + if ( nrSpaces > 0 ) { // there are more spaces + if ( !str.isEmpty() ) + addTextNode( str ); + str = QString::null; + startElement( "text:s" ); + if ( nrSpaces > 1 ) // it's 1 by default + addAttribute( "text:c", nrSpaces ); + endElement(); + } + } + nrSpaces = 0; + leadingSpace = false; + } + switch ( ch.unicode() ) { + case '\t': + if ( !str.isEmpty() ) + addTextNode( str ); + str = QString::null; + startElement( "text:tab" ); + if ( tabCache.contains( i ) ) + addAttribute( "text:tab-ref", tabCache[i] + 1 ); + endElement(); + break; + case '\n': + if ( !str.isEmpty() ) + addTextNode( str ); + str = QString::null; + startElement( "text:line-break" ); + endElement(); + break; + case ' ': + if ( i == 0 ) + leadingSpace = true; + ++nrSpaces; + break; + default: + str += text[i]; + break; + } + } + // either we still have text in str or we have spaces in nrSpaces + if ( !str.isEmpty() ) { + addTextNode( str ); + } + if ( nrSpaces > 0 ) { // there are more spaces + startElement( "text:s" ); + if ( nrSpaces > 1 ) // it's 1 by default + addAttribute( "text:c", nrSpaces ); + endElement(); + } +} diff --git a/lib/store/KoXmlWriter.h b/lib/store/KoXmlWriter.h new file mode 100644 index 00000000..232f9a65 --- /dev/null +++ b/lib/store/KoXmlWriter.h @@ -0,0 +1,281 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <faure@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef XMLWRITER_H +#define XMLWRITER_H + +#include <qstring.h> +#include <qvaluestack.h> +#include <qmap.h> +#include <koffice_export.h> + +class QIODevice; + +/** + * A class for writing out XML (to any QIODevice), with a special attention on performance. + * The XML is being written out along the way, which avoids requiring the entire + * document in memory (like QDom does), and avoids using QTextStream at all + * (which in Qt3 has major performance issues when converting to utf8). + */ +class KOSTORE_EXPORT KoXmlWriter +{ +public: + /** + * Create a KoXmlWriter instance to write out an XML document into + * the given QIODevice. + */ + KoXmlWriter( QIODevice* dev, int indentLevel = 0 ); + + /// Destructor + ~KoXmlWriter(); + + QIODevice *device() const { return m_dev; } + + /** + * Start the XML document. + * This writes out the \<?xml?\> tag with utf8 encoding, and the DOCTYPE. + * @param rootElemName the name of the root element, used in the DOCTYPE tag. + * @param publicId the public identifier, e.g. "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" + * @param systemId the system identifier, e.g. "office.dtd" or a full URL to it. + */ + void startDocument( const char* rootElemName, const char* publicId = 0, const char* systemId = 0 ); + + /// Call this to terminate an XML document. + void endDocument(); + + /** + * Start a new element, as a child of the current element. + * @param tagName the name of the tag. Warning: this string must + * remain alive until endElement, no copy is internally made. + * Usually tagName is a string constant so this is no problem anyway. + * @param indentInside if set to false, there will be no indentation inside + * this tag. This is useful for elements where whitespace matters. + */ + void startElement( const char* tagName, bool indentInside = true ); + + /** + * Overloaded version of addAttribute( const char*, const char* ), + * which is a bit slower because it needs to convert @p value to utf8 first. + */ + inline void addAttribute( const char* attrName, const QString& value ) { + addAttribute( attrName, value.utf8() ); + } + /** + * Add an attribute whose value is an integer + */ + inline void addAttribute( const char* attrName, int value ) { + QCString str; + str.setNum( value ); + addAttribute( attrName, str.data() ); + } + /** + * Add an attribute whose value is an unsigned integer + */ + inline void addAttribute( const char* attrName, uint value ) { + QCString str; + str.setNum( value ); + addAttribute( attrName, str.data() ); + } + /** + * Add an attribute whose value is a floating point number + * The number is written out with the highest possible precision + * (unlike QString::number and setNum, which default to 6 digits) + */ + void addAttribute( const char* attrName, double value ); + /** + * Add an attribute which represents a distance, measured in pt + * The number is written out with the highest possible precision + * (unlike QString::number and setNum, which default to 6 digits), + * and the unit name ("pt") is appended to it. + */ + void addAttributePt( const char* attrName, double value ); + + /// Overloaded version of the one taking a const char* argument, for convenience + inline void addAttribute( const char* attrName, const QCString& value ) { + addAttribute( attrName, value.data() ); + } + /** + * Add an attribute to the current element. + */ + void addAttribute( const char* attrName, const char* value ); + /** + * Terminate the current element. After this you should start a new one (sibling), + * add a sibling text node, or close another one (end of siblings). + */ + void endElement(); + /** + * Overloaded version of addTextNode( const char* ), + * which is a bit slower because it needs to convert @p str to utf8 first. + */ + inline void addTextNode( const QString& str ) { + addTextNode( str.utf8() ); + } + /// Overloaded version of the one taking a const char* argument + inline void addTextNode( const QCString& cstr ) { + addTextNode( cstr.data() ); + } + /** + * @brief Adds a text node as a child of the current element. + * + * This is appends the litteral content of @p str to the contents of the element. + * E.g. addTextNode( "foo" ) inside a \<p\> element gives \<p\>foo\</p\>, + * and startElement( "b" ); endElement( "b" ); addTextNode( "foo" ) gives \<p\>\<b/\>foo\</p\> + */ + void addTextNode( const char* cstr ); + + /** + * @brief Adds a processing instruction + * + * This writes a processing instruction, like <?foo bar blah?>, where foo + * is the target, and the rest is the data. + * + * Processing instructions are used in XML to keep processor-specific + * information in the text of the document. + */ + void addProcessingInstruction( const char* cstr ); + + /** + * This is quite a special-purpose method, not for everyday use. + * It adds a complete element (with its attributes and child elements) + * as a child of the current element. The string is supposed to be escaped + * for XML already, so it will usually come from another KoXmlWriter. + */ + void addCompleteElement( const char* cstr ); + + /** + * This is quite a special-purpose method, not for everyday use. + * It adds a complete element (with its attributes and child elements) + * as a child of the current element. The iodevice is supposed to be escaped + * for XML already, so it will usually come from another KoXmlWriter. + * This is usually used with KTempFile. + */ + void addCompleteElement( QIODevice* dev ); + + // #### Maybe we want to subclass KoXmlWriter for manifest files. + /** + * Special helper for writing "manifest" files + * This is equivalent to startElement/2*addAttribute/endElement + * This API will probably have to change (or not be used anymore) + * when we add support for encrypting/signing. + * @note OASIS-specific + */ + void addManifestEntry( const QString& fullPath, const QString& mediaType ); + + /** + * Special helper for writing config item into settings.xml + * @note OASIS-specific + */ + void addConfigItem( const QString & configName, const QString& value ); + /// @note OASIS-specific + void addConfigItem( const QString & configName, bool value ); + /// @note OASIS-specific + void addConfigItem( const QString & configName, int value ); + /// @note OASIS-specific + void addConfigItem( const QString & configName, double value ); + /// @note OASIS-specific + void addConfigItem( const QString & configName, long value ); + /// @note OASIS-specific + void addConfigItem( const QString & configName, short value ); + + // TODO addConfigItem for datetime and base64Binary + + /** + * @brief Adds a text span as nodes of the current element. + * + * Unlike KoXmlWriter::addTextNode it handles tabulations, linebreaks, + * and multiple spaces by using the appropriate OASIS tags. + * + * @param text the text to write + * + * @note OASIS-specific + */ + void addTextSpan( const QString& text ); + /** + * Overloaded version of addTextSpan which takes an additional tabCache map. + * @param text the text to write + * @param tabCache optional map allowing to find a tab for a given character index + * @note OASIS-specific + */ + void addTextSpan( const QString& text, const QMap<int, int>& tabCache ); + + /** + * @return the current indentation level. + * Useful when creating a sub-KoXmlWriter (see addCompleteElement) + */ + int indentLevel() const { return m_tags.size() + m_baseIndentLevel; } + +private: + struct Tag { + Tag( const char* t = 0, bool ind = true ) + : tagName( t ), hasChildren( false ), lastChildIsText( false ), + openingTagClosed( false ), indentInside( ind ) {} + const char* tagName; + bool hasChildren; ///< element or text children + bool lastChildIsText; ///< last child is a text node + bool openingTagClosed; ///< true once the '\>' in \<tag a="b"\> is written out + bool indentInside; ///< whether to indent the contents of this tag + }; + + /// Write out \n followed by the number of spaces required. + void writeIndent(); + + // writeCString is much faster than writeString. + // Try to use it as much as possible, especially with constants. + void writeString( const QString& str ); + + // unused and possibly incorrect if length != size + //inline void writeCString( const QCString& cstr ) { + // m_dev->writeBlock( cstr.data(), cstr.size() - 1 ); + //} + + inline void writeCString( const char* cstr ) { + m_dev->writeBlock( cstr, qstrlen( cstr ) ); + } + inline void writeChar( char c ) { + m_dev->putch( c ); + } + inline void closeStartElement( Tag& tag ) { + if ( !tag.openingTagClosed ) { + tag.openingTagClosed = true; + writeChar( '>' ); + } + } + char* escapeForXML( const char* source, int length ) const; + bool prepareForChild(); + void prepareForTextNode(); + void init(); + + QIODevice* m_dev; + QValueStack<Tag> m_tags; + int m_baseIndentLevel; + + class Private; + Private *d; + + char* m_indentBuffer; // maybe make it static, but then it needs a KStaticDeleter, + // and would eat 1K all the time... Maybe refcount it :) + char* m_escapeBuffer; // can't really be static if we want to be thread-safe + static const int s_escapeBufferLen = 10000; + + KoXmlWriter( const KoXmlWriter & ); // forbidden + KoXmlWriter& operator=( const KoXmlWriter & ); // forbidden +}; + +#endif /* XMLWRITER_H */ + diff --git a/lib/store/KoZipStore.cpp b/lib/store/KoZipStore.cpp new file mode 100644 index 00000000..58aa2181 --- /dev/null +++ b/lib/store/KoZipStore.cpp @@ -0,0 +1,237 @@ +/* This file is part of the KDE project + Copyright (C) 2000-2002 David Faure <faure@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "KoZipStore.h" + +#include <qbuffer.h> + +#include <kzip.h> +#include <kdebug.h> +#include <kdeversion.h> +#include <kurl.h> +#include <kio/netaccess.h> +#if ! KDE_IS_VERSION( 3, 4, 1 ) +#include <qdir.h> +#include <qfileinfo.h> +#endif + +KoZipStore::KoZipStore( const QString & _filename, Mode _mode, const QCString & appIdentification ) +{ + kdDebug(s_area) << "KoZipStore Constructor filename = " << _filename + << " mode = " << int(_mode) + << " mimetype = " << appIdentification << endl; + + m_pZip = new KZip( _filename ); + +#if ! KDE_IS_VERSION( 3, 4, 1 ) + // Workaround for KZip KSaveFile double deletion in kdelibs-3.4, + // when trying to write to a non-writable directory. + QDir dir( QFileInfo( _filename ).dir() ); + if (_mode == Write && !QFileInfo( dir.path() ).isWritable() ) + { + kdWarning(s_area) << dir.path() << " isn't writable" << endl; + m_bGood = false; + m_currentDir = 0; + KoStore::init( _mode ); + } + else +#endif + { + m_bGood = init( _mode, appIdentification ); // open the zip file and init some vars + } +} + +KoZipStore::KoZipStore( QIODevice *dev, Mode mode, const QCString & appIdentification ) +{ + m_pZip = new KZip( dev ); + m_bGood = init( mode, appIdentification ); +} + +KoZipStore::KoZipStore( QWidget* window, const KURL & _url, const QString & _filename, Mode _mode, const QCString & appIdentification ) +{ + kdDebug(s_area) << "KoZipStore Constructor url" << _url.prettyURL() + << " filename = " << _filename + << " mode = " << int(_mode) + << " mimetype = " << appIdentification << endl; + + m_url = _url; + m_window = window; + + if ( _mode == KoStore::Read ) + { + m_fileMode = KoStoreBase::RemoteRead; + m_localFileName = _filename; + + } + else + { + m_fileMode = KoStoreBase::RemoteWrite; + m_localFileName = "/tmp/kozip"; // ### FIXME with KTempFile + } + + m_pZip = new KZip( m_localFileName ); + m_bGood = init( _mode, appIdentification ); // open the zip file and init some vars +} + +KoZipStore::~KoZipStore() +{ + kdDebug(s_area) << "KoZipStore::~KoZipStore" << endl; + m_pZip->close(); + delete m_pZip; + + // Now we have still some job to do for remote files. + if ( m_fileMode == KoStoreBase::RemoteRead ) + { + KIO::NetAccess::removeTempFile( m_localFileName ); + } + else if ( m_fileMode == KoStoreBase::RemoteWrite ) + { + KIO::NetAccess::upload( m_localFileName, m_url, m_window ); + // ### FIXME: delete temp file + } +} + +bool KoZipStore::init( Mode _mode, const QCString& appIdentification ) +{ + KoStore::init( _mode ); + m_currentDir = 0; + bool good = m_pZip->open( _mode == Write ? IO_WriteOnly : IO_ReadOnly ); + + if ( good && _mode == Read ) + good = m_pZip->directory() != 0; + else if ( good && _mode == Write ) + { + //kdDebug(s_area) << "KoZipStore::init writing mimetype " << appIdentification << endl; + + m_pZip->setCompression( KZip::NoCompression ); + m_pZip->setExtraField( KZip::NoExtraField ); + // Write identification + (void)m_pZip->writeFile( "mimetype", "", "", appIdentification.length(), appIdentification.data() ); + m_pZip->setCompression( KZip::DeflateCompression ); + // We don't need the extra field in KOffice - so we leave it as "no extra field". + } + return good; +} + +bool KoZipStore::openWrite( const QString& name ) +{ +#if 0 + // Prepare memory buffer for writing + m_byteArray.resize( 0 ); + m_stream = new QBuffer( m_byteArray ); + m_stream->open( IO_WriteOnly ); + return true; +#endif + m_stream = 0L; // Don't use! + return m_pZip->prepareWriting( name, "", "" /*m_pZip->rootDir()->user(), m_pZip->rootDir()->group()*/, 0 ); +} + +bool KoZipStore::openRead( const QString& name ) +{ + const KArchiveEntry * entry = m_pZip->directory()->entry( name ); + if ( entry == 0L ) + { + //kdWarning(s_area) << "Unknown filename " << name << endl; + //return KIO::ERR_DOES_NOT_EXIST; + return false; + } + if ( entry->isDirectory() ) + { + kdWarning(s_area) << name << " is a directory !" << endl; + //return KIO::ERR_IS_DIRECTORY; + return false; + } + // Must cast to KZipFileEntry, not only KArchiveFile, because device() isn't virtual! + const KZipFileEntry * f = static_cast<const KZipFileEntry *>(entry); + delete m_stream; + m_stream = f->device(); + m_iSize = f->size(); + return true; +} + +Q_LONG KoZipStore::write( const char* _data, Q_ULONG _len ) +{ + if ( _len == 0L ) return 0; + //kdDebug(s_area) << "KoZipStore::write " << _len << endl; + + if ( !m_bIsOpen ) + { + kdError(s_area) << "KoStore: You must open before writing" << endl; + return 0L; + } + if ( m_mode != Write ) + { + kdError(s_area) << "KoStore: Can not write to store that is opened for reading" << endl; + return 0L; + } + + m_iSize += _len; + if ( m_pZip->writeData( _data, _len ) ) // writeData returns a bool! + return _len; + return 0L; +} + +bool KoZipStore::closeWrite() +{ + kdDebug(s_area) << "Wrote file " << m_sName << " into ZIP archive. size " + << m_iSize << endl; + return m_pZip->doneWriting( m_iSize ); +#if 0 + if ( !m_pZip->writeFile( m_sName , "user", "group", m_iSize, m_byteArray.data() ) ) + kdWarning( s_area ) << "Failed to write " << m_sName << endl; + m_byteArray.resize( 0 ); // save memory + return true; +#endif +} + +bool KoZipStore::enterRelativeDirectory( const QString& dirName ) +{ + if ( m_mode == Read ) { + if ( !m_currentDir ) { + m_currentDir = m_pZip->directory(); // initialize + Q_ASSERT( m_currentPath.isEmpty() ); + } + const KArchiveEntry *entry = m_currentDir->entry( dirName ); + if ( entry && entry->isDirectory() ) { + m_currentDir = dynamic_cast<const KArchiveDirectory*>( entry ); + return m_currentDir != 0; + } + return false; + } + else // Write, no checking here + return true; +} + +bool KoZipStore::enterAbsoluteDirectory( const QString& path ) +{ + if ( path.isEmpty() ) + { + m_currentDir = 0; + return true; + } + m_currentDir = dynamic_cast<const KArchiveDirectory*>( m_pZip->directory()->entry( path ) ); + Q_ASSERT( m_currentDir ); + return m_currentDir != 0; +} + +bool KoZipStore::fileExists( const QString& absPath ) const +{ + const KArchiveEntry *entry = m_pZip->directory()->entry( absPath ); + return entry && entry->isFile(); +} diff --git a/lib/store/KoZipStore.h b/lib/store/KoZipStore.h new file mode 100644 index 00000000..9bde4fe3 --- /dev/null +++ b/lib/store/KoZipStore.h @@ -0,0 +1,61 @@ +/* This file is part of the KDE project + Copyright (C) 2002 David Faure <faure@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef koZipStore_h +#define koZipStore_h + +#include "KoStoreBase.h" + +class KZip; +class KArchiveDirectory; +class KURL; + +class KoZipStore : public KoStoreBase +{ +public: + KoZipStore( const QString & _filename, Mode _mode, const QCString & appIdentification ); + KoZipStore( QIODevice *dev, Mode mode, const QCString & appIdentification ); + /** + * KURL-constructor + * @todo saving not completely implemented (fixed temporary file) + * @since 1.4 + */ + KoZipStore( QWidget* window, const KURL& _url, const QString & _filename, Mode _mode, const QCString & appIdentification ); + ~KoZipStore(); + + virtual Q_LONG write( const char* _data, Q_ULONG _len ); +protected: + virtual bool init( Mode _mode, const QCString& appIdentification ); + virtual bool openWrite( const QString& name ); + virtual bool openRead( const QString& name ); + virtual bool closeWrite(); + virtual bool closeRead() { return true; } + virtual bool enterRelativeDirectory( const QString& dirName ); + virtual bool enterAbsoluteDirectory( const QString& path ); + virtual bool fileExists( const QString& absPath ) const; + + /// The archive + KZip * m_pZip; + + /** In "Read" mode this pointer is pointing to the + current directory in the archive to speed up the verification process */ + const KArchiveDirectory* m_currentDir; +}; + +#endif diff --git a/lib/store/Makefile.am b/lib/store/Makefile.am new file mode 100644 index 00000000..ac6d5478 --- /dev/null +++ b/lib/store/Makefile.am @@ -0,0 +1,13 @@ + +SUBDIRS = . tests + +KDE_CXXFLAGS = $(USE_RTTI) +lib_LTLIBRARIES = libkstore.la +INCLUDES = $(KOFFICECORE_INCLUDES) $(all_includes) + +####### Files + +libkstore_la_LIBADD = $(LIB_KIO) +libkstore_la_SOURCES = KoStore.cpp KoTarStore.cpp KoDirectoryStore.cpp KoZipStore.cpp KoStoreDrag.cpp KoStoreBase.cpp KoXmlWriter.cpp +libkstore_la_LDFLAGS = $(all_libraries) -version-info 3:0:0 $(KDE_LDFLAGS) -no-undefined +include_HEADERS = KoStore.h KoStoreDevice.h KoXmlWriter.h diff --git a/lib/store/SPEC b/lib/store/SPEC new file mode 100644 index 00000000..e72286a4 --- /dev/null +++ b/lib/store/SPEC @@ -0,0 +1,122 @@ +------------------------------------------------------------------------------- +- - +- KOffice Storage Format Specification - Version 2.3 - +- - +- by Werner, last changed: 20020306 by Werner Trobin - +- - +- History : - +- Version 1.0 : binary store - +- Version 2.0 : tar.gz store - +- Version 2.1 : cleaned up - +- version 2.2 : shaheed Put each part into its own directory to allow - +- one filter to easily embed the results of another - +- and also to have its own documentinfo etc. - +- Added description of naming convention. - +- Version 2.3 : werner Allow the usage of relative links. It is now - +- possible to refer to any "embedded" image or part - +- via a plain relative URL as you all know it. - +- - +------------------------------------------------------------------------------- + +The purpose of this document is to define a common KOffice Storage Structure. +Torben, Reggie, and all the others agreed on storing embedded KOffice Parts +and binary data (e.g. pictures, movies, sounds) via a simple tar.gz-structure. +The support class for the tar format is kdelibs/kio/ktar.*, written by Torben +and finished by David. + +The obvious benefits of this type of storage are: + - It's 100% non- proprietary as it uses only the already available formats + (XML, pictures, tar.gz, ...) and tools (tar, gzip). + - It enables anybody to edit the document directly; for instance, to update + an image (faster than launching the application), or to write scripts + that generate KOffice documents ! :) + - It is also easy to write an import filter for any other office-suite + application out there by reading the tar.gz file and extracting the XML out + of it (at the worst, the user can extract the XML file by himself, but then + the import loses embedded Parts and pictures). + +The tar.gz format also generates much smaller files than the old binary +store, since everything's gzipped. + +Name of the KOffice Files +------------------------- + +As some people suggested, using a "tgz"-ending is confusing; it's been dropped. +Instead, we use the "normal" endings like ".kwd", ".ksp", ".kpr", etc. To recognize +KOffice documents without a proper extension David Faure <faure@kde.org> +added some magic numbers to the gzip header (to see what I'm talking about +please use the "file" command on a KOffice document or see +http://lists.kde.org/?l=koffice-devel&m=98609092618214&w=2); + +External Structure +------------------ + +Here is a simple example to demonstrate the structure of a KOffice document. +Assume you have to write a lab-report. You surely will have some text, the +readings, some formulas and a few pictures (e.g. circuit diagram,...). +The main document will be a KWord-file. In this file you embed some KSpread- +tables, some KChart-diagramms, the KFormulas, and some picture-frames. You save +the document as "lab-report.kwd". Here is what the contents of the +tar.gz file will look like : + +lab-report.kwd: +--------------- +maindoc.xml -- The main XML file containing the KWord document. +documentinfo.xml -- Author and other "metadata" for KWord document. +pictures/ -- Pictures embedded in the main KWord document. +pictures/picture0.jpg +pictures/picture1.bmp +cliparts/ -- Cliparts embedded in the main KWord document. +cliparts/clipart0.wmf +part0/maindoc.xml -- for instance a KSpread embedded table. +part0/documentinfo.xml -- Author and other "metadata" for KSpread table. +part0/part1/maindoc.xml -- say a KChart diagram within the KSpread table. +part1/maindoc.xml -- say a KChart diagram. +part2/maindoc.xml -- why not a KIllustrator drawing. +part2/pictures/ -- Pictures embedded in the KIllustrator document. +part2/pictures/picture0.jpg +part2/pictures/picture1.bmp +part2/cliparts/ -- Cliparts embedded in the KIllustrator document. +part2/cliparts/clipart0.wmf +... + +Internal Name +------------- + +- Absolute references: + The API provided by this specification does not require application writers + or filter writers to know the details of the external structure: + + tar:/documentinfo.xml is saved as documentinfo.xml + tar:/0 is saved as part0/maindoc.xml + tar:/0/documentinfo.xml is saved as part0/documentinfo.xml + tar:/0/1 is saved as part0/part1/maindoc.xml + tar:/0/1/pictures/picture0.png + is saved as part0/part1/pictures/picture0.png + tar:/Table1/0 is saved as Table1/part0/maindoc.xml + + Note that this is the structure as of version 2.2 of this specification. + The only other format shipped with KDE2.0 is converted (on reading) to look + like this through the services of the "Internal Name". + + If the document does not contain any other Parts or pictures, then the + maindoc.xml and documentinfo.xml files are tarred and gzipped alone, + and saved with the proper extension (.kwd for KWord, .ksp for KSpread, + etc.). + The plan is to use relative paths everywhere, so please try not to use + absolute paths unless neccessary. + +- Relative references: + To allow parts to be self-contained, and to ease the work of filter + developers version 2.3 features relative links within the storage. + This means that the KoStore now has a "state" as in "there is something + like a current directory". You can specify a link like + "pictures/picture0.png" and depending on the current directory this will + be mapped to some absolute path. The surrounding code has to ensure that + the current path is maintained correctly, but due to that we can get rid + of the ugly prefix thingy. + + +Thank you for your attention, +Werner <trobin@kde.org> and David <faure@kde.org> +(edited by Chris Lee <lee@azsites.com> for grammer, spelling, and formatting) diff --git a/lib/store/fix_storage.pl b/lib/store/fix_storage.pl new file mode 100644 index 00000000..8b13a397 --- /dev/null +++ b/lib/store/fix_storage.pl @@ -0,0 +1,217 @@ +#!/usr/bin/perl -w + +use strict; + +# A small script to convert current-style KOffice tar storages to storages +# compatible with KOffice 1.0 and KOffice 1.1(.1) + +# Note to developers: +# Add the PID (in Perl: $$ ) to all fixed temporary directory/file names, +# so that this script can be run multiple times at once. + +# Holds the directory tree +my @rootdir; +my $tmpdir = "/tmp/kofficeconverter$$" ; +print "Using temporary directory... $tmpdir\n"; +# Holds the source/dest of the files to fix +my @needFixing; + +# Walk the whole archive and collect information about the files +# This creates one array, containing another array for every directory +# we found (recursively). Additionally this array holding a directory +# holds the name of the directory and the path. +sub explore { + my($path) = @_; + my(@dir); + + print " Exploring: $path\n"; + chdir($path); + opendir(DIR, $path) || die "Couldn't open the directory: $!"; + my @contents = readdir(DIR); + my $i = 0; + foreach(@contents) { + if($_ eq "." || $_ eq "..") { + next; # we're not intersted in . and .. + } + if(-d $_) { + $dir[$i] = [ $_, $path, [ explore($path . '/' . $_) ] ]; + chdir($path); # back to the directory where we come from + } + else { + $dir[$i] = $_; + } + $i = $i + 1; + } + closedir(DIR); + return @dir; +} + +# Dumps the scary datastructure we built +sub dumpTree { + my(@dir) = @_; + foreach(@dir) { + if(ref($_) eq 'ARRAY') { + print $_->[0], " (", $_->[1], ")\n"; + dumpTree(@{$_->[2]}); + } + else { + print $_ . "\n"; + } + } +} + +# Finds the files where we have to fix part references (->maindoc.xml) +sub findCandidates { + my($dref, $currentdir, $parentdir) = @_; + my @dir = @{$dref}; + #print "current: $currentdir, parentdir: $parentdir\n"; + foreach(@dir) { + if(ref($_) eq 'ARRAY') { + #print $_->[0], " (", $_->[1], ")\n"; + findCandidates(\@{$_->[2]}, $_->[0], $_->[1]); + } + else { + if($_ =~ m/maindoc\.xml/) { + my $source = $parentdir . '/' . $currentdir . "/maindoc.xml"; + my $dest = $parentdir . '/' . $currentdir . ".xml"; + push(@needFixing, [ $source, $dest ]); + } + } + } +} + +# No need to move around elements of the root directory, these are handled +# separately anyway. Therefore we call findCandidates only on subdirs +sub findMainDocuments { + foreach(@rootdir) { + if(ref($_) eq 'ARRAY') { + findCandidates(\@{$_->[2]}, $_->[0], $_->[1]); + } + } +} + +# Factorizes the common regexp code between maindoc.xml and parts +sub fixLine { + my($line, $prefix) = @_; + + if($line =~ m/(\s*\<object\s+mime=\"[^\"]*\"\s+url=\")([^\"]*)(\".*)/) { + return $1 . $prefix . $2 . $3 . "\n"; + } + elsif($line =~ m/(\s*\<OBJECT\s+mime=\"[^\"]*\"\s+url=\")([^\"]*)(\".*)/) { + return $1 . $prefix . $2 . $3 . "\n"; + } + elsif($line =~ m/(\s*\<KEY\s+.*\s+)filename(=\"[^\"]*\".*)/) { + my($tmp) = $1 . "key" . $2 . "\n"; + if($tmp =~ m/(\s*\<KEY\s+.*\s+name=\")([^\"]*)(\".*)/) { + return $1 . $prefix . $2 . $3 . "\n"; + } + return $tmp; + } +# Replace pictures by images, as cliparts will never work with only this script. + elsif($line =~ m%\s*\<PICTURE%) { + $line =~ s%\<PICTURES%\<PIXMAPS% ; + $line =~ s%\<PICTURE%\<IMAGE% ; + } + elsif($line =~ m%\s*\</PICTURE%) { + $line =~ s%\</PICTURES%\</PIXMAPS% ; + $line =~ s%\</PICTURE%\</IMAGE% ; + } + elsif($line =~ m%\s*\<BACKPICTUREKEY%) { + $line =~ s%\<BACKPICTUREKEY%\<BACKPIXKEY% ; + } + return $line; +} + +# Walks through all the documents and fixes links. "Fixes" all the +# candidates we found +sub fixLinks { + for my $item (@needFixing) { + my $prefix = substr $item->[0], length($tmpdir)+1; + $prefix =~ m,^(.*?)(maindoc\.xml),; + $prefix = "tar:/" . $1; + open(SOURCE, "<$item->[0]") || die "Couldn't open the source file: $!\n"; + open(DEST, ">$item->[1]") || die "Couldn't open the destination file: $!\n"; + while(<SOURCE>) { + print DEST fixLine($_, $prefix); + } + close(SOURCE); + close(DEST); + } +} + +# Get rid of the moved files +sub removeOldFiles { + foreach(@needFixing) { + system("rm -rf $_->[0]"); + } +} + +# Special case for the main document as we have to use a temporary +# file and stuff like that. We only have to fix part references here. +sub fixMainDocument { + open(SOURCE, "<$tmpdir/maindoc.xml"); + open(DEST, ">$tmpdir/tmp.xml"); + while(<SOURCE>) { + print DEST fixLine($_, "tar:/"); + } + close(SOURCE); + close(DEST); + system("mv $tmpdir/tmp.xml $tmpdir/maindoc.xml"); +} + +################################################## +# The execution starts here +################################################## +if($#ARGV != 1) { + print "Script to convert current storages to KOffice 1.0/1.1.x compatible ones.\n"; + print "Usage: perl fix_storage.pl <inputfile> <outputfile>\n"; + exit(1); +} + +# remember where we came from +chomp(my $cwd = `pwd`); + +# clean up properly +system("rm -rf $tmpdir"); +mkdir $tmpdir || die "Couldn't create tmp directory: $!\n"; + + +print "Trying to detect the type of archive... "; +my($mime) = `file -i -z $ARGV[0]`; + +if($mime =~ m,application/x-tar,) { + print "tar.gz\n"; + print "Uncompressing the archive...\n"; + system("tar -C $tmpdir -xzf $ARGV[0]"); +} +elsif($mime =~ m,application/x-zip,) { + print "zip\n"; + print "Uncompressing the archive...\n"; + system("unzip -qq -d $tmpdir $ARGV[0]"); +} + +print "Browsing the directory structure...\n"; +@rootdir = explore($tmpdir); + +# debugging +#dumpTree(@rootdir); + +print "Find candidates for moving...\n"; +findMainDocuments(); + +print "Moving and fixing relative links...\n"; +fixLinks(); +removeOldFiles(); +fixMainDocument(); + +print "Creating the archive...\n"; +chdir($tmpdir); +system("tar czf tmp$$.tgz *"); +chdir ($cwd); +system("mv $tmpdir/tmp$$.tgz $ARGV[1]"); + +print "Cleaning up...\n"; +# clean up properly +system("rm -rf $tmpdir"); + +print "Done.\n"; diff --git a/lib/store/tests/Makefile.am b/lib/store/tests/Makefile.am new file mode 100644 index 00000000..caeed44e --- /dev/null +++ b/lib/store/tests/Makefile.am @@ -0,0 +1,18 @@ +####### General stuff + +KDE_CXXFLAGS = $(USE_RTTI) +INCLUDES= -I$(srcdir)/.. -I$(srcdir)/../../kofficecore $(all_includes) + +####### Files + +check_PROGRAMS = storage_test xmlwritertest storedroptest +TESTS = storage_test xmlwritertest + +storage_test_SOURCES = storage_test.cpp +storage_test_LDADD = ../libkstore.la + +xmlwritertest_SOURCES = xmlwritertest.cpp +xmlwritertest_LDADD = ../libkstore.la + +storedroptest_SOURCES = storedroptest.cpp +storedroptest_LDADD = ../libkstore.la diff --git a/lib/store/tests/storage_test.cpp b/lib/store/tests/storage_test.cpp new file mode 100644 index 00000000..6fd03bf3 --- /dev/null +++ b/lib/store/tests/storage_test.cpp @@ -0,0 +1,220 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Werner Trobin <trobin@kde.org> + + 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <qfile.h> +#include <qdir.h> +#include <kcmdlineargs.h> +#include <kapplication.h> + +#include <KoStore.h> +#include <kdebug.h> +#include <stdlib.h> + +#include <string.h> + +namespace { + const char* const test1 = "This test checks whether we're able to write to some arbitrary directory.\n"; + const char* const testDir = "0"; + const char* const testDirResult = "0/"; + const char* const test2 = "This time we try to append the given relative path to the current dir.\n"; + const char* const test3 = "<xml>Hello World</xml>"; + const char* const testDir2 = "test2/with/a"; + const char* const testDir2Result = "0/test2/with/a/"; + const char* const test4 = "<xml>Heureka, it works</xml>"; + + const char* const badStorage = "Ooops, bad storage???"; + const char* const unableToOpen = "Couldn't open storage!"; + const char* const brokenPath = "Path handling broken!"; + const char* const unableToRead = "Couldn't read stream back!"; +} + +int cleanUp( KoStore* store, const QString& testFile, const char* error ) +{ + QFile::remove( testFile ); + delete store; + kdDebug() << error << endl; + return 1; +} + +int test( const char* testName, KoStore::Backend backend, const QString& testFile ) +{ + if ( QFile::exists( testFile ) ) + QFile::remove( testFile ); + QDir dirTest( testFile ); + if ( dirTest.exists() ) { + system( QCString( "rm -rf " ) + QFile::encodeName( testFile ) ); // QDir::rmdir isn't recursive! + } + + kdDebug() << "======================="<<testName<<"====================================" << endl; + KoStore* store = KoStore::createStore( testFile, KoStore::Write, "", backend ); + if ( store->bad() ) + return cleanUp( store, testFile, badStorage ); + + if ( store->open( "test1/with/a/relative/dir.txt" ) ) { + for ( int i = 0; i < 100; ++i ) + store->write( test1, strlen( test1 ) ); + store->close(); + } + else + return cleanUp( store, testFile, unableToOpen ); + + store->enterDirectory( testDir ); + if ( store->currentPath() != QString( testDirResult ) ) + return cleanUp( store, testFile, brokenPath ); + + if ( store->open( "test2/with/a/relative/dir.txt" ) ) { + for ( int i = 0; i < 100; ++i ) + store->write( test2, strlen( test2 ) ); + store->close(); + } + else + return cleanUp( store, testFile, unableToOpen ); + + if ( store->open( "root" ) ) { + store->write( test3, strlen( test3 ) ); + store->close(); + } + else + return cleanUp( store, testFile, unableToOpen ); + + store->enterDirectory( testDir2 ); + if ( store->currentPath() != QString( testDir2Result ) ) + return cleanUp( store, testFile, brokenPath ); + + if ( store->open( "root" ) ) { + store->write( test4, strlen( test4 ) ); + store->close(); + } + else + return cleanUp( store, testFile, unableToOpen ); + + if ( store->isOpen() ) + store->close(); + delete store; + + kdDebug() << "===========================================================" << endl; + + store = KoStore::createStore( testFile, KoStore::Read, "", backend ); + if ( store->bad() ) + return cleanUp( store, testFile, badStorage ); + + if ( store->open( "test1/with/a/relative/dir.txt" ) ) { + QIODevice* dev = store->device(); + int i = 0, lim = strlen( test1 ), count = 0; + while ( static_cast<char>( dev->getch() ) == test1[i++] ) { + if ( i == lim ) { + i = 0; + ++count; + } + } + store->close(); + if ( count != 100 ) + return cleanUp( store, testFile, unableToRead ); + } + else + return cleanUp( store, testFile, unableToOpen ); + + store->enterDirectory( testDir ); + if ( store->currentPath() != QString( testDirResult ) ) + return cleanUp( store, testFile, brokenPath ); + + if ( store->open( "test2/with/a/relative/dir.txt" ) ) { + QIODevice* dev = store->device(); + int i = 0, lim = strlen( test2 ), count = 0; + while ( static_cast<char>( dev->getch() ) == test2[i++] ) { + if ( i == lim ) { + i = 0; + ++count; + } + } + store->close(); + if ( count != 100 ) + return cleanUp( store, testFile, unableToRead ); + } + else + return cleanUp( store, testFile, unableToOpen ); + + store->enterDirectory( testDir2 ); + store->pushDirectory(); + + while ( store->leaveDirectory() ); + store->enterDirectory( testDir ); + if ( store->currentPath() != QString( testDirResult ) ) + return cleanUp( store, testFile, brokenPath ); + + if ( store->open( "root" ) ) { + if ( store->size() == 22 ) { + QIODevice* dev = store->device(); + unsigned int i = 0; + while ( static_cast<char>( dev->getch() ) == test3[i++] ); + store->close(); + if ( ( i - 1 ) != strlen( test3 ) ) + return cleanUp( store, testFile, unableToRead ); + } + else { + kdError() << "Wrong size! maindoc.xml is " << store->size() << " should be 22." << endl; + delete store; + return 1; + } + } + else { + kdError() << "Couldn't open storage!" << endl; + delete store; + return 1; + } + + store->popDirectory(); + if ( store->currentPath() != QString( testDir2Result ) ) + return cleanUp( store, testFile, brokenPath ); + + if ( store->open( "root" ) ) { + char buf[29]; + store->read( buf, 28 ); + buf[28] = '\0'; + store->close(); + if ( strncmp( buf, test4, 28 ) != 0 ) + return cleanUp( store, testFile, unableToRead ); + } + else + return cleanUp( store, testFile, unableToOpen ); + + if ( store->isOpen() ) + store->close(); + delete store; + QFile::remove( testFile ); + + kdDebug() << "===========================================================" << endl; + return 0; +} + +int main( int argc, char **argv ) +{ + KCmdLineArgs::init( argc, argv, "storage_test", "A test for the KoStore classes", "1" ); + KApplication app( argc, argv ); + + // KZip (due to KSaveFile) doesn't support relative filenames + // So use $PWD as base for the paths explicitely. + const QString testDir = QDir::currentDirPath(); + if ( test( "Tar", KoStore::Tar, testDir+"test.tgz" ) != 0 ) + return 1; + if ( test( "Directory", KoStore::Directory, testDir+"testdir/maindoc.xml" ) != 0 ) + return 1; + if ( test( "Zip", KoStore::Zip, testDir+"test.zip" ) != 0 ) + return 1; +} diff --git a/lib/store/tests/storedroptest.cpp b/lib/store/tests/storedroptest.cpp new file mode 100644 index 00000000..0510ea62 --- /dev/null +++ b/lib/store/tests/storedroptest.cpp @@ -0,0 +1,138 @@ +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <KoStore.h> +#include <qtextbrowser.h> +#include <qstringlist.h> +#include <qbuffer.h> +#include <qclipboard.h> + +class StoreDropTest : public QTextBrowser +{ +public: + StoreDropTest( QWidget* parent ); +protected: + virtual void contentsDragEnterEvent( QDragEnterEvent * e ); + virtual void contentsDragMoveEvent( QDragMoveEvent * e ); + virtual void contentsDropEvent( QDropEvent * e ); + virtual void keyPressEvent( QKeyEvent * e ); + virtual void paste(); +private: + bool processMimeSource( QMimeSource* ev ); + void showZipContents( QByteArray data, const char* mimeType, bool oasis ); + QString loadTextFile( KoStore* store, const QString& fileName ); +}; + +int main( int argc, char** argv ) +{ + KApplication::disableAutoDcopRegistration(); + KCmdLineArgs::init(argc, argv, "storedroptest", 0, 0, 0, 0); + KApplication app; + + StoreDropTest* window = new StoreDropTest( 0 ); + window->resize( 500, 500 ); + window->show(); + + QObject::connect( qApp, SIGNAL( lastWindowClosed() ), qApp, SLOT( quit() ) ); + return app.exec(); +} + +StoreDropTest::StoreDropTest( QWidget* parent ) + : QTextBrowser( parent ) +{ + setText( "KoStore drop/paste test\nDrop or paste a selection from a KOffice application into this widget to see the ZIP contents" ); + setAcceptDrops( true ); +} + +void StoreDropTest::contentsDragEnterEvent( QDragEnterEvent * ev ) +{ + ev->acceptAction(); +} + +void StoreDropTest::contentsDragMoveEvent( QDragMoveEvent * ev ) +{ + ev->acceptAction(); +} + +void StoreDropTest::keyPressEvent( QKeyEvent * e ) +{ + if ( ( ( e->state() & ShiftButton ) && e->key() == Key_Insert ) || + ( ( e->state() & ControlButton ) && e->key() == Key_V ) ) + paste(); + //else + // QTextBrowser::keyPressEvent( e ); +} + +void StoreDropTest::paste() +{ + qDebug( "paste" ); + QMimeSource* m = QApplication::clipboard()->data(); + if ( !m ) + return; + processMimeSource( m ); +} + +void StoreDropTest::contentsDropEvent( QDropEvent *ev ) +{ + if ( processMimeSource( ev ) ) + ev->acceptAction(); + else + ev->ignore(); +} + +bool StoreDropTest::processMimeSource( QMimeSource* ev ) +{ + const QCString acceptMimeType = "application/vnd.oasis.opendocument."; + const char* fmt; + QStringList formats; + for (int i=0; (fmt = ev->format(i)); i++) { + formats += fmt; + bool oasis = QString( fmt ).startsWith( acceptMimeType ); + if ( oasis || QString( fmt ) == "application/x-kpresenter" ) { + QByteArray data = ev->encodedData( fmt ); + showZipContents( data, fmt, oasis ); + return true; + } + } + setText( "No acceptable format found. All I got was:\n" + formats.join( "\n" ) ); + return false; +} + +void StoreDropTest::showZipContents( QByteArray data, const char* mimeType, bool oasis ) +{ + if ( data.isEmpty() ) { + setText( "No data!" ); + return; + } + QBuffer buffer( data ); + KoStore * store = KoStore::createStore( &buffer, KoStore::Read ); + if ( store->bad() ) { + setText( "Invalid ZIP!" ); + return; + } + store->disallowNameExpansion(); + + QString txt = QString( "Valid ZIP file found for format " ) + mimeType + "\n"; + + if ( oasis ) { + txt += loadTextFile( store, "content.xml" ); + txt += loadTextFile( store, "styles.xml" ); + txt += loadTextFile( store, "settings.xml" ); + txt += loadTextFile( store, "META-INF/manifest.xml" ); + } else { + txt += loadTextFile( store, "maindoc.xml" ); + } + setText( txt ); +} + +QString StoreDropTest::loadTextFile( KoStore* store, const QString& fileName ) +{ + if ( !store->open( fileName ) ) + return QString( "%1 not found\n" ).arg( fileName ); + + QByteArray data = store->device()->readAll(); + store->close(); + QString txt = QString( "Found %1: \n" ).arg( fileName ); + txt += QString::fromUtf8( data.data(), data.size() ); + txt += "\n"; + return txt; +} diff --git a/lib/store/tests/xmlwritertest.cpp b/lib/store/tests/xmlwritertest.cpp new file mode 100644 index 00000000..dad86f8f --- /dev/null +++ b/lib/store/tests/xmlwritertest.cpp @@ -0,0 +1,144 @@ +#include "xmlwritertest.h" + +#include "KoXmlWriter.h" + +#include <qapplication.h> +#include <qfile.h> +#include <qdatetime.h> + +static const int numParagraphs = 30000; +void speedTest() +{ + QTime time; + time.start(); + QString paragText = QString::fromUtf8( "This is the text of the paragraph. I'm including a euro sign to test encoding issues: €" ); + QCString styleName = "Heading 1"; + + QFile out( QString::fromLatin1( "out5.xml" ) ); + if ( out.open(IO_WriteOnly) ) + { + KoXmlWriter writer( &out ); + writer.startDocument( "rootelem" ); + writer.startElement( "rootelem" ); + for ( int i = 0 ; i < numParagraphs ; ++i ) + { + writer.startElement( "paragraph" ); + writer.addAttribute( "text:style-name", styleName ); + writer.addTextNode( paragText ); + writer.endElement(); + } + writer.endElement(); + writer.endDocument(); + } + out.close(); + qDebug( "writing %i XML elements using KoXmlWriter: %i ms", numParagraphs, time.elapsed() ); +} + +int main( int argc, char** argv ) { + QApplication app( argc, argv, QApplication::Tty ); + + TEST_BEGIN( 0, 0 ); + TEST_END( "framework test", "<r/>\n" ); + + TEST_BEGIN( "-//KDE//DTD kword 1.3//EN", "http://www.koffice.org/DTD/kword-1.3.dtd" ); + TEST_END( "doctype test", "<!DOCTYPE r PUBLIC \"-//KDE//DTD kword 1.3//EN\" \"http://www.koffice.org/DTD/kword-1.3.dtd\">\n<r/>\n" ); + + TEST_BEGIN( 0, 0 ); + writer.addAttribute( "a", "val" ); + writer.addAttribute( "b", "<\">" ); + writer.addAttribute( "c", -42 ); + writer.addAttribute( "d", 1234.56789012345 ); + writer.addAttributePt( "e", 1234.56789012345 ); + TEST_END( "attributes test", "<r a=\"val\" b=\"<">\" c=\"-42\" d=\"1234.56789012345\" e=\"1234.56789012345pt\"/>\n" ); + + TEST_BEGIN( 0, 0 ); + writer.startElement( "m" ); + writer.endElement(); + TEST_END( "empty element test", "<r>\n <m/>\n</r>\n" ); + + TEST_BEGIN( 0, 0 ); + writer.startElement( "a" ); + writer.startElement( "b" ); + writer.startElement( "c" ); + writer.endElement(); + writer.endElement(); + writer.endElement(); + TEST_END( "indent test", "<r>\n <a>\n <b>\n <c/>\n </b>\n </a>\n</r>\n" ); + + TEST_BEGIN( 0, 0 ); + writer.startElement( "a" ); + writer.startElement( "b", false /*no indent*/ ); + writer.startElement( "c" ); + writer.endElement(); + writer.addTextNode( "te" ); + writer.addTextNode( "xt" ); + writer.endElement(); + writer.endElement(); + TEST_END( "textnode test", "<r>\n <a>\n <b><c/>text</b>\n </a>\n</r>\n" ); + + TEST_BEGIN( 0, 0 ); + writer.startElement( "p", false /*no indent*/ ); + writer.addTextSpan( QString::fromLatin1( " \t\n foo " ) ); + writer.endElement(); + TEST_END( "textspan test", "<r>\n" + " <p><text:s text:c=\"3\"/><text:tab/><text:line-break/> foo<text:s text:c=\"2\"/></p>\n" + "</r>\n" ); + + TEST_BEGIN( 0, 0 ); + writer.startElement( "p", false /*no indent*/ ); + QMap<int, int> tabCache; + tabCache.insert( 3, 0 ); + writer.addTextSpan( QString::fromUtf8( " \t\n foö " ), tabCache ); + writer.endElement(); + TEST_END( "textspan with tabcache", "<r>\n" + " <p><text:s text:c=\"3\"/><text:tab text:tab-ref=\"1\"/><text:line-break/> foö<text:s text:c=\"2\"/></p>\n" + "</r>\n" ); + + TEST_BEGIN( 0, 0 ); + writer.startElement( "p", false /*no indent*/ ); + writer.addProcessingInstruction( "opendocument foobar" ); + writer.addTextSpan( QString::fromLatin1( "foo" ) ); + writer.endElement(); + TEST_END( "processinginstruction test", "<r>\n" + " <p><?opendocument foobar?>foo</p>\n" + "</r>\n" ); + + TEST_BEGIN( 0, 0 ); + writer.addManifestEntry( QString::fromLatin1( "foo/bar/blah" ), QString::fromLatin1( "mime/type" ) ); + TEST_END( "addManifestEntry", "<r>\n <manifest:file-entry manifest:media-type=\"mime/type\" manifest:full-path=\"foo/bar/blah\"/>\n</r>\n" ); + + int sz = 15000; // must be more than KoXmlWriter::s_escapeBufferLen + QCString x( sz ); + x.fill( 'x', sz ); + x += '&'; + QCString expected = "<r a=\""; + expected += x + "amp;\"/>\n"; + TEST_BEGIN( 0, 0 ); + writer.addAttribute( "a", x ); + TEST_END( "escaping long cstring", expected.data() ); + + QString longPath; + for ( uint i = 0 ; i < 1000 ; ++i ) + longPath += QString::fromLatin1( "M10 10L20 20 " ); + expected = "<r a=\""; + expected += longPath.utf8() + "\"/>\n"; + TEST_BEGIN( 0, 0 ); + writer.addAttribute( "a", longPath ); + TEST_END( "escaping long qstring", expected.data() ); + + + TEST_BEGIN( 0, 0 ); + bool val = true; + int num = 1; + double numdouble = 5.0; + writer.addConfigItem( QString::fromLatin1( "TestConfigBool" ), val ); + writer.addConfigItem( QString::fromLatin1( "TestConfigInt" ), num ); + writer.addConfigItem( QString::fromLatin1( "TestConfigDouble" ), numdouble ); + TEST_END( "test config", "<r>\n" + " <config:config-item config:name=\"TestConfigBool\" config:type=\"boolean\">true</config:config-item>\n" + " <config:config-item config:name=\"TestConfigInt\" config:type=\"int\">1</config:config-item>\n" + " <config:config-item config:name=\"TestConfigDouble\" config:type=\"double\">5</config:config-item>\n" + "</r>\n" ); + + speedTest(); +} diff --git a/lib/store/tests/xmlwritertest.h b/lib/store/tests/xmlwritertest.h new file mode 100644 index 00000000..c5dc76e7 --- /dev/null +++ b/lib/store/tests/xmlwritertest.h @@ -0,0 +1,46 @@ +#ifndef XMLWRITERTEST_H +#define XMLWRITERTEST_H + +#define QT_NO_CAST_ASCII + +// Those macros are in a separate header file in order to share them +// with kofficecore/tests/kogenstylestest.cpp + +#include <qbuffer.h> +#include <qregexp.h> + +#define TEST_BEGIN(publicId,systemId) \ + { \ + QCString cstr; \ + QBuffer buffer( cstr ); \ + buffer.open( IO_WriteOnly ); \ + { \ + KoXmlWriter writer( &buffer ); \ + writer.startDocument( "r", publicId, systemId ); \ + writer.startElement( "r" ) + +#define TEST_END(testname, expected) \ + writer.endElement(); \ + writer.endDocument(); \ + } \ + buffer.putch( '\0' ); /*null-terminate*/ \ + QCString expectedFull( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ); \ + expectedFull += expected; \ + if ( cstr == expectedFull ) \ + qDebug( "%s OK", testname ); \ + else { \ + qDebug( "%s FAILED!", testname ); \ + QCString s1 = cstr; \ + QCString s2 = expectedFull; \ + if ( s1.length() != s2.length() ) \ + qDebug( "got length %d, expected %d", s1.length(), s2.length() ); \ + s1.replace( QRegExp( QString::fromLatin1( "[x]{1000}" ) ), "[x]*1000" ); \ + s2.replace( QRegExp( QString::fromLatin1( "[x]{1000}" ) ), "[x]*1000" ); \ + qDebug( "%s", s1.data() ); \ + qDebug( "Expected:\n%s", s2.data() ); \ + return 1; /*exit*/ \ + } \ + } + + +#endif diff --git a/lib/store/update_kzip.sh b/lib/store/update_kzip.sh new file mode 100644 index 00000000..bc47fc09 --- /dev/null +++ b/lib/store/update_kzip.sh @@ -0,0 +1,26 @@ +#!/bin/sh +header="// GENERATED FILE. Do not edit! Generated from kzip.cpp by $0" +echo "$header" > kozip.cpp +cat ../../../kdelibs/kio/kio/kzip.cpp >> kozip.cpp || exit 1 +echo "$header" > kozip.h +cat ../../../kdelibs/kio/kio/kzip.h >> kozip.h || exit 1 +echo "$header" > kofilterdev.cpp +cat ../../../kdelibs/kio/kio/kfilterdev.cpp >> kofilterdev.cpp || exit 1 +echo "$header" > kofilterdev.h +cat ../../../kdelibs/kio/kio/kfilterdev.h >> kofilterdev.h || exit 1 +echo "$header" > kolimitediodevice.h +cat ../../../kdelibs/kio/kio/klimitediodevice.h >> kolimitediodevice.h || exit 1 + +perl -pi -e 's/KZip/KoZip/g' kozip.cpp kozip.h +perl -pi -e 's/kzip\.h/kozip\.h/' kozip.cpp +perl -pi -e 's/KFilterDev/KoFilterDev/g' kofilterdev.cpp kofilterdev.h kozip.cpp +perl -pi -e 's/kfilterdev\.h/kofilterdev\.h/' kofilterdev.cpp kozip.cpp +perl -pi -e 's/KLimitedIODevice/KoLimitedIODevice/g' kolimitediodevice.h kozip.cpp +perl -pi -e 's/klimitediodevice\.h/kolimitediodevice\.h/g' kozip.cpp + +perl -pi -e 's/closeArchive/closeArchiveHack/' kozip.cpp kozip.h +perl -pi -e 'if (/.include .karchive\.h./) { print "\#define private public // need protected for m_open access for the HACK, and public for setting KArchiveFile::m_size\n$_\#undef private\n"; }' kozip.h +perl -pi -e 'if (/virtual bool doneWriting/) { print $_; $_ = " virtual void close(); // HACK for misplaced closeArchive() call in KDE-3.0''s KArchive\n virtual bool closeArchive() { return true; } // part of the same hack\n"; }' kozip.h +perl -pi -e 'if (/KoZip::doneWriting/) { print "void KoZip::close() { // HACK for misplaced closeArchive() call in KDE-3.0''s KArchive\n if (!isOpened()) return;\n closeArchiveHack();\n device()->close();\n m_open = false;\n}\n\n"; }' kozip.cpp +perl -pi -e 'if (/d->m_currentFile->setSize\(size\);/) { $_ = " d->m_currentFile->m_size = size;\n"; }' kozip.cpp + |