palmdoc.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2001 Ariya Hidayat <ariyahidayat@yahoo.de>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18*/
19
20#include "palmdoc.h"
21
22#include <tqcstring.h>
23#include <tqdatetime.h>
24#include <tqptrlist.h>
25#include <tqstring.h>
26
27PalmDoc::PalmDoc(): PalmDB()
28{
29 m_result = PalmDoc::OK;
30 setText( TQString() );
31}
32
33PalmDoc::~PalmDoc()
34{
35}
36
37bool PalmDoc::load( const char* filename )
38{
39 bool ok;
40
41 ok = PalmDB::load( filename );
42 if( !ok )
43 {
44 m_result = PalmDoc::ReadError;
45 return false;
46 }
47
48 if( type() != "TEXt" )
49 {
50 tqDebug( "Type is \"%s\", not \"TEXt\", so this is not Palm DOC!", type().latin1() );
51 m_result = PalmDoc::InvalidFormat;
52 return false;
53 }
54
55 if( creator() != "REAd" )
56 {
57 tqDebug( "Creator is \"%s\", not \"REAd\", so this is not Palm DOC!",
58 creator().latin1() );
59 m_result = PalmDoc::InvalidFormat;
60 return false;
61 }
62
63 // must have at least two records
64 if( records.count() < 2 )
65 {
66 tqDebug( "Palm DOC has at least 2 records!" );
67 m_result = PalmDoc::InvalidFormat;
68 return false;
69 }
70
71 // the very first record is DOC header
72 // NOTE: this is not PDB header (which is handled in PalmDB) !
73 TQByteArray header( *records.at( 0 ) );
74
75 // format of the DOC
76 int format = (header[0]<<8) + header[1];
77 tqDebug( "DOC format: %d (%s)", format,
78 (format==1) ? "Plain" : (format==2) ? "Compressed" : "Unknown" );
79
80 // supported is only Plain or Compressed
81 if( ( format != 1 ) && ( format != 2 ) )
82 {
83 tqDebug( "Unknown format of document!" );
84 m_result = PalmDoc::InvalidFormat;
85 return false;
86 }
87
88 // initialize
89 setText( TQString() );
90
91 // assemble the records
92 TQByteArray rec;
93 unsigned i = 0;
94 for( unsigned r = 1; r < records.count(); r++ )
95 {
96 TQByteArray *p = records.at( r );
97 if( !p ) continue;
98 rec.resize( rec.size() + p->size() );
99 for( unsigned s=0; s<p->size(); s++ )
100 rec[i++] = p->at( s );
101 }
102
103 // if the text is compressed, then uncompress
104 if( format == 2 )
105 setText( uncompress( rec ) );
106
107 // if the text is not compressed, simply append as string
108 if( format == 1 )
109 setText( TQString::fromLatin1( rec.data(),rec.count() ) );
110
111 // done
112 m_result = OK;
113 return true;
114}
115
116bool PalmDoc::save( const char* filename )
117{
118 // set proper database type and creator
119 setType( "TEXt" );
120 setCreator( "REAd" );
121
122 // "touch" the database :-)
123 setModificationDate( TQDateTime::currentDateTime() );
124
125 // Palm record size is always 4 KB
126 unsigned recsize = 4096;
127
128 // compress the text
129 TQByteArray data = compress( text() );
130
131 // prepare the records
132 records.clear();
133 for( unsigned i=0; i<data.count(); )
134 {
135 TQByteArray* ptr = new TQByteArray;
136 unsigned rs = data.count() - i;
137 if( rs > recsize ) rs = recsize;
138 ptr->resize( rs );
139 for( unsigned m=0; m<rs; m++ )
140 (*ptr)[m] = data[i++];
141 records.append( ptr );
142 }
143
144 // prepare the header
145 TQByteArray header( 16 );
146 int docsize = m_text.length();
147 header[0] = 0; header[1] = 2; // 1=plain, 2=compressed
148 header[2] = header[3] = 0; // reserved word, set to 0
149 header[4] = (docsize >> 24) & 255; // uncompressed size
150 header[5] = (docsize >> 16) & 255;
151 header[6] = (docsize >> 8) & 255;
152 header[7] = docsize & 255;
153 header[8] = records.count()>> 8; // no of records
154 header[9] = records.count() & 255;
155 header[10] = recsize >>8; // record size
156 header[11] = recsize & 255;
157 header[12] = header[13] = 0;
158 header[14] = header[15] = 0;
159
160 // header should be the very first record
161 records.prepend( new TQByteArray( header ) );
162
163 // write to file
164 bool ok = PalmDB::save( filename );
165 if( !ok )
166 {
167 m_result = WriteError;
168 return false;
169 }
170
171 // done
172 m_result = OK;
173 return true;
174}
175
176// TODO describe in brief about compression algorithm
177TQByteArray PalmDoc::compress( const TQString& text )
178{
179 TQByteArray result;
180 unsigned textlen = text.length();
181 const char *ctext = text.latin1();
182 unsigned int i, j;
183
184 // we don't know the compressed size yet
185 // therefore allocate buffer big enough
186 result.resize( textlen );
187
188 for( i=j=0; i<textlen; )
189 {
190 int horizon = 2047;
191 int start = (i < horizon) ? 0 : i-horizon;
192 bool match = false;
193 int match_pos=0, match_len=0;
194
195 // look for match in the buffer
196 for( int back = i-1; (!match) && (back > start); back-- )
197 if( ctext[i] == ctext[back] )
198 if( ctext[i+1] == ctext[back+1] )
199 if( ctext[i+2] == ctext[back+2] )
200 {
201 match = true;
202 match_pos = i-back;
203 match_len = 3;
204
205 if( i+3 < textlen )
206 if( ctext[i+3] == ctext[back+3] )
207 {
208 match_len = 4;
209 if( i+4 < textlen )
210 if( ctext[i+4] == ctext[back+4] )
211 {
212 match_len = 5;
213 }
214 }
215
216 }
217
218 if( match )
219 {
220 unsigned char p = 0x80 | ((match_pos >> 5)&0x3f);
221 unsigned char q = ((match_pos & 0x1f) << 3) | (match_len-3);
222 result[j++] = p;
223 result[j++] = q;
224 i+= match_len;
225 }
226 else
227 {
228 char ch = ctext[i++] & 0x7f;
229 bool space_pack = false;
230
231 if( ch == 0x20 )
232 if ( i<textlen )
233 if( ctext[i] >= 0x40 )
234 space_pack = true;
235
236 if( !space_pack ) result[j++] = ch;
237 else result[j++] = ctext[i++] | 0x80;
238 }
239
240 }
241
242 result.resize( j );
243
244 return result;
245}
246
247#define INRANGE(v,p,q) ((v)>=(p))&&((v)<=(q))
248
249// TODO describe in brief about decompression algorithm
250TQString PalmDoc::uncompress( TQByteArray rec )
251{
252 TQString result;
253
254 for( unsigned i = 0; i < rec.size(); i++ )
255 {
256 unsigned char c = rec[i];
257
258 if( INRANGE(c,1,8) )
259 {
260 i++;
261 if( i < rec.size() )
262 for( unsigned char v = rec[i]; c>0; c-- )
263 result.append( v );
264 }
265
266 else if( INRANGE(c,0x09,0x7F) )
267 result.append( c );
268
269 else if( INRANGE(c,0xC0,0xFF) )
270 result.append( 32 ).append( c^ 0x80 );
271
272 else if( INRANGE(c,0x80,0xBF) )
273 {
274 unsigned char d = rec[++i];
275 int back = (((c<<8)+d) & 0x3fff) >> 3;
276 int count = (d & 7) + 3;
277 if( result.length()-back >= 0 )
278 for(; count>0; count-- )
279 result.append( result[result.length()-back] );
280 }
281
282 }
283
284 return result;
285}