summaryrefslogtreecommitdiffstats
path: root/lib/store
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-01-20 01:29:50 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-01-20 01:29:50 +0000
commit8362bf63dea22bbf6736609b0f49c152f975eb63 (patch)
tree0eea3928e39e50fae91d4e68b21b1e6cbae25604 /lib/store
downloadkoffice-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.cpp117
-rw-r--r--lib/store/KoDirectoryStore.h54
-rw-r--r--lib/store/KoStore.cpp629
-rw-r--r--lib/store/KoStore.h381
-rw-r--r--lib/store/KoStoreBase.cpp29
-rw-r--r--lib/store/KoStoreBase.h51
-rw-r--r--lib/store/KoStoreDevice.h88
-rw-r--r--lib/store/KoStoreDrag.cpp35
-rw-r--r--lib/store/KoStoreDrag.h55
-rw-r--r--lib/store/KoTarStore.cpp206
-rw-r--r--lib/store/KoTarStore.h65
-rw-r--r--lib/store/KoXmlWriter.cpp427
-rw-r--r--lib/store/KoXmlWriter.h281
-rw-r--r--lib/store/KoZipStore.cpp237
-rw-r--r--lib/store/KoZipStore.h61
-rw-r--r--lib/store/Makefile.am13
-rw-r--r--lib/store/SPEC122
-rw-r--r--lib/store/fix_storage.pl217
-rw-r--r--lib/store/tests/Makefile.am18
-rw-r--r--lib/store/tests/storage_test.cpp220
-rw-r--r--lib/store/tests/storedroptest.cpp138
-rw-r--r--lib/store/tests/xmlwritertest.cpp144
-rw-r--r--lib/store/tests/xmlwritertest.h46
-rw-r--r--lib/store/update_kzip.sh26
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 &quot; and &apos;
+ 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, "&lt;", 4 );
+ destination += 4;
+ break;
+ case 62: // >
+ memcpy( destination, "&gt;", 4 );
+ destination += 4;
+ break;
+ case 34: // "
+ memcpy( destination, "&quot;", 6 );
+ destination += 6;
+ break;
+#if 0 // needed?
+ case 39: // '
+ memcpy( destination, "&apos;", 6 );
+ destination += 6;
+ break;
+#endif
+ case 38: // &
+ memcpy( destination, "&amp;", 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=\"&lt;&quot;&gt;\" 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
+