filters

palmdoc.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2001 Ariya Hidayat <ariyahidayat@yahoo.de>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017  * Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "palmdoc.h"
00021 
00022 #include <qcstring.h>
00023 #include <qdatetime.h>
00024 #include <qptrlist.h>
00025 #include <qstring.h>
00026 
00027 PalmDoc::PalmDoc(): PalmDB()
00028 {
00029   m_result = PalmDoc::OK;
00030   setText( QString::null );
00031 }
00032 
00033 PalmDoc::~PalmDoc()
00034 {
00035 }
00036 
00037 bool PalmDoc::load( const char* filename )
00038 {
00039   bool ok;
00040 
00041   ok = PalmDB::load( filename );
00042   if( !ok )
00043   {
00044     m_result = PalmDoc::ReadError;
00045     return FALSE;
00046   }
00047 
00048   if( type() != "TEXt" )
00049   {
00050     qDebug( "Type is \"%s\", not \"TEXt\", so this is not Palm DOC!", type().latin1() );
00051     m_result = PalmDoc::InvalidFormat;
00052     return FALSE;
00053   }
00054 
00055   if( creator() != "REAd" )
00056   {
00057     qDebug( "Creator is \"%s\", not \"REAd\", so this is not Palm DOC!",
00058       creator().latin1() );
00059     m_result = PalmDoc::InvalidFormat;
00060     return FALSE;
00061   }
00062 
00063   // must have at least two records
00064   if( records.count() < 2 )
00065   {
00066     qDebug( "Palm DOC has at least 2 records!" );
00067     m_result = PalmDoc::InvalidFormat;
00068     return FALSE;
00069   }
00070 
00071   // the very first record is DOC header
00072   // NOTE: this is not PDB header (which is handled in PalmDB) !
00073   QByteArray header( *records.at( 0 ) );
00074 
00075   // format of the DOC
00076   int format = (header[0]<<8) + header[1];
00077   qDebug( "DOC format: %d (%s)", format,
00078      (format==1) ? "Plain" : (format==2) ? "Compressed" : "Unknown" );
00079 
00080   // supported is only Plain or Compressed
00081   if( ( format != 1 ) && ( format != 2 ) )
00082   {
00083     qDebug( "Unknown format of document!" );
00084     m_result = PalmDoc::InvalidFormat;
00085     return FALSE;
00086   }
00087 
00088   // initialize
00089   setText( QString::null );
00090 
00091   // assemble the records
00092   QByteArray rec;
00093   unsigned i = 0;
00094   for( unsigned r = 1; r < records.count(); r++ )
00095   {
00096      QByteArray *p = records.at( r );
00097      if( !p ) continue;
00098      rec.resize( rec.size() + p->size() );
00099      for( unsigned s=0; s<p->size(); s++ )
00100        rec[i++] = p->at( s );
00101   }
00102 
00103   // if the text is compressed, then uncompress
00104   if( format == 2 )
00105     setText( uncompress( rec ) );
00106 
00107   // if the text is not compressed, simply append as string
00108   if( format == 1 )
00109       setText( QString::fromLatin1( rec.data(),rec.count() ) );
00110 
00111   // done
00112   m_result = OK;
00113   return TRUE;
00114 }
00115 
00116 bool PalmDoc::save( const char* filename )
00117 {
00118   // set proper database type and creator
00119   setType( "TEXt" );
00120   setCreator( "REAd" );
00121 
00122   // "touch" the database :-)
00123   setModificationDate( QDateTime::currentDateTime() ); 
00124 
00125   // Palm record size is always 4 KB
00126   unsigned recsize = 4096;
00127 
00128   // compress the text
00129   QByteArray data = compress( text() );  
00130 
00131   // prepare the records
00132   records.clear();
00133   for( unsigned i=0; i<data.count(); )
00134   {
00135     QByteArray* ptr = new QByteArray;
00136     unsigned rs = data.count() - i;
00137     if( rs > recsize ) rs = recsize;
00138     ptr->resize( rs );
00139     for( unsigned m=0; m<rs; m++ )
00140       (*ptr)[m] = data[i++];
00141     records.append( ptr );
00142   } 
00143 
00144   // prepare the header 
00145   QByteArray header( 16 );
00146   int docsize = m_text.length();
00147   header[0] = 0; header[1] = 2;  // 1=plain, 2=compressed 
00148   header[2] = header[3] = 0; // reserved word, set to 0
00149   header[4] = (docsize >> 24) & 255; // uncompressed size
00150   header[5] = (docsize >> 16) & 255;
00151   header[6] = (docsize >> 8) & 255;
00152   header[7] = docsize & 255;  
00153   header[8] = records.count()>> 8; // no of records
00154   header[9] = records.count() & 255;
00155   header[10] = recsize >>8; // record size
00156   header[11] = recsize & 255;
00157   header[12] = header[13] = 0;
00158   header[14] = header[15] = 0;
00159 
00160   // header should be the very first record
00161   records.prepend( new QByteArray( header ) );
00162 
00163   // write to file
00164   bool ok = PalmDB::save( filename );
00165   if( !ok )
00166   {
00167     m_result = WriteError;
00168     return FALSE;
00169   }
00170 
00171   // done
00172   m_result = OK;
00173   return TRUE;
00174 }
00175 
00176 // TODO describe in brief about compression algorithm
00177 QByteArray PalmDoc::compress( const QString& text )
00178 {
00179   QByteArray result;
00180   unsigned textlen = text.length();
00181   const char *ctext =  text.latin1();
00182   unsigned int i, j;
00183 
00184   // we don't know the compressed size yet
00185   // therefore allocate buffer big enough
00186   result.resize( textlen );
00187 
00188   for( i=j=0; i<textlen;  )
00189   {
00190     int horizon = 2047;
00191     int start = (i < horizon) ? 0 : i-horizon;
00192     bool match = false;
00193     int match_pos=0, match_len=0;
00194 
00195     // look for match in the buffer
00196     for( int back = i-1; (!match) && (back > start); back-- )
00197       if( ctext[i] == ctext[back] )
00198       if( ctext[i+1] == ctext[back+1] )
00199       if( ctext[i+2] == ctext[back+2] )
00200       {
00201          match = true;
00202          match_pos = i-back;
00203          match_len = 3;
00204 
00205          if( i+3 < textlen )
00206            if( ctext[i+3] == ctext[back+3] )
00207            {
00208               match_len = 4;
00209               if( i+4 < textlen )
00210                 if( ctext[i+4] == ctext[back+4] )
00211                 {
00212                   match_len = 5;
00213                 }
00214            }
00215 
00216       }
00217 
00218    if( match )
00219    {
00220      unsigned char p = 0x80 | ((match_pos >> 5)&0x3f);
00221      unsigned char q = ((match_pos & 0x1f) << 3) | (match_len-3);
00222      result[j++] = p;
00223      result[j++] = q;
00224      i+= match_len;
00225    }
00226    else
00227    {
00228      char ch = ctext[i++] & 0x7f;
00229      bool space_pack = false;
00230 
00231      if( ch == 0x20 )
00232        if ( i<textlen )
00233          if( ctext[i] >= 0x40 )
00234             space_pack = true;
00235 
00236      if( !space_pack ) result[j++] = ch;
00237      else result[j++] = ctext[i++] | 0x80;
00238    }
00239 
00240   }
00241 
00242   result.resize( j );
00243 
00244   return result;
00245 }
00246 
00247 #define INRANGE(v,p,q) ((v)>=(p))&&((v)<=(q))
00248 
00249 // TODO describe in brief about decompression algorithm
00250 QString PalmDoc::uncompress( QByteArray rec )
00251 {
00252   QString result;
00253 
00254   for( unsigned i = 0; i < rec.size(); i++ )
00255   {
00256     unsigned char c = rec[i];
00257 
00258     if( INRANGE(c,1,8) )
00259     {
00260       i++;
00261       if( i < rec.size() )
00262          for( unsigned char v = rec[i]; c>0; c-- )
00263             result.append( v );
00264     }
00265 
00266     else if( INRANGE(c,0x09,0x7F) )
00267       result.append( c );
00268 
00269     else if( INRANGE(c,0xC0,0xFF) )
00270       result.append( 32 ).append( c^ 0x80 );
00271 
00272     else if( INRANGE(c,0x80,0xBF) )
00273     {
00274       unsigned char d = rec[++i];
00275       int back = (((c<<8)+d) & 0x3fff) >> 3;
00276       int count = (d & 7) + 3;
00277       if( result.length()-back >= 0 )
00278         for(; count>0; count-- )
00279           result.append( result[result.length()-back] );
00280     }
00281 
00282   }
00283 
00284   return result;
00285 }
KDE Home | KDE Accessibility Home | Description of Access Keys