1
/* This file is part of Strigi Desktop Search
3
* Copyright (C) 2006 Jos van den Oever <jos@vandenoever.info>
5
* This library is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU Library General Public
7
* License as published by the Free Software Foundation; either
8
* version 2 of the License, or (at your option) any later version.
10
* This library is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
* Library General Public License for more details.
15
* You should have received a copy of the GNU Library General Public License
16
* along with this library; see the file COPYING.LIB. If not, write to
17
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18
* Boston, MA 02110-1301, USA.
21
#include "cluceneindexreader.h"
22
#include <strigi/strigiconfig.h>
23
#include <strigi/query.h>
24
#include <strigi/queryparser.h>
25
#include <strigi/variant.h>
26
#include <strigi/textutils.h>
27
#include "cluceneindexmanager.h"
28
#include "timeofday.h"
29
#include "tcharutils.h"
30
#include <CLucene/search/QueryFilter.h>
31
#include <CLucene/index/Terms.h>
32
#include <CLucene/store/RAMDirectory.h>
33
#include <strigi/fieldtypes.h>
39
using lucene::search::Hits;
40
using lucene::search::IndexSearcher;
41
using lucene::document::Document;
42
using lucene::document::Field;
43
using lucene::index::Term;
44
using lucene::index::TermDocs;
45
using lucene::index::TermEnum;
46
using lucene::search::TermQuery;
47
using lucene::search::WildcardQuery;
48
using lucene::search::BooleanQuery;
49
using lucene::search::Query;
50
using lucene::search::RangeQuery;
51
using lucene::search::QueryFilter;
52
using lucene::search::HitCollector;
53
using lucene::util::BitSet;
54
using lucene::document::DocumentFieldEnumeration;
55
using Strigi::IndexedDocument;
56
using Strigi::Variant;
57
using Strigi::FieldRegister;
61
class HitCounter : public HitCollector {
64
void collect (const int32_t doc, const float_t score) { m_count++; }
66
HitCounter() :m_count(0) {}
67
int32_t count() const { return m_count; }
70
class CLuceneIndexReader::Private {
72
static const wchar_t* systemlocation();
73
static const wchar_t* mtime();
74
static const wchar_t* mimetype();
75
static const wchar_t* size();
76
static const wchar_t* content();
77
static const wchar_t* parentlocation();
78
CLuceneIndexReader& reader;
79
Private(CLuceneIndexReader& r) :reader(r) {}
81
static Term* createTerm(const wchar_t* name, const string& value);
82
static Term* createKeywordTerm(const wchar_t* name, const string& value);
83
static Term* createWildCardTerm(const wchar_t* name, const string& value);
84
Query* createQuery(const Strigi::Query& query);
85
Query* createSimpleQuery(const Strigi::Query& query);
86
static Query* createSingleFieldQuery(const string& field,
87
const Strigi::Query& query);
88
Query* createNoFieldQuery(const Strigi::Query& query);
89
Query* createMultiFieldQuery(const Strigi::Query& query);
90
BooleanQuery* createBooleanQuery(const Strigi::Query& query);
91
static void addField(lucene::document::Field* field, IndexedDocument&);
92
Variant getFieldValue(lucene::document::Field* field, Variant::Type) const;
94
vector<IndexedDocument> strigiSpecial(const string& command);
97
CLuceneIndexReader::Private::systemlocation() {
98
const static wstring s(utf8toucs2(FieldRegister::pathFieldName));
102
CLuceneIndexReader::Private::mtime() {
103
const static wstring s(utf8toucs2(FieldRegister::mtimeFieldName));
107
CLuceneIndexReader::Private::mimetype() {
108
const static wstring s(utf8toucs2(FieldRegister::mimetypeFieldName));
112
CLuceneIndexReader::Private::size() {
113
const static wstring s(utf8toucs2(FieldRegister::sizeFieldName));
117
CLuceneIndexReader::Private::parentlocation() {
118
const static wstring s(utf8toucs2(FieldRegister::parentLocationFieldName));
122
CLuceneIndexReader::Private::content() {
123
const static wstring s(utf8toucs2(FieldRegister::contentFieldName));
127
CLuceneIndexReader::CLuceneIndexReader(CLuceneIndexManager* m,
128
const string& dir) :manager(m), p(new Private(*this)), dbdir(dir),
135
CLuceneIndexReader::~CLuceneIndexReader() {
140
CLuceneIndexReader::openReader() {
145
reader = lucene::index::IndexReader::open(manager->directory);
147
// "READER at %s: %i\n", dbdir.c_str(), reader->numDocs());
148
} catch (CLuceneError& err) {
149
fprintf(stderr, "could not create reader %s: %s\n", dbdir.c_str(),
155
CLuceneIndexReader::closeReader() {
156
if (reader == 0) return;
159
} catch (CLuceneError& err) {
160
fprintf(stderr, "could not close clucene: %s\n", err.what());
166
CLuceneIndexReader::checkReader(bool enforceCurrent) {
167
struct timeval mtime = manager->indexMTime();
168
if (mtime.tv_sec != otime.tv_sec || mtime.tv_usec != otime.tv_usec) {
169
if (enforceCurrent) {
174
gettimeofday(&now, 0);
175
if (now.tv_sec - otime.tv_sec > 60) {
184
return reader != NULL;
188
typedef map<wstring, wstring> CLuceneIndexReaderFieldMapType;
190
typedef map<string, string> CLuceneIndexReaderFieldMapType;
192
CLuceneIndexReaderFieldMapType CLuceneIndexReaderFieldMap;
194
void CLuceneIndexReader::addMapping(const TCHAR* from, const TCHAR* to){
195
CLuceneIndexReaderFieldMap[from] = to;
198
CLuceneIndexReader::mapId(const TCHAR* id) {
199
if (CLuceneIndexReaderFieldMap.size() == 0) {
200
string contentID(FieldRegister::contentFieldName.c_str());
201
wstring cID(utf8toucs2(contentID));
202
addMapping(_T(""), cID.c_str());
207
CLuceneIndexReaderFieldMapType::iterator itr
208
= CLuceneIndexReaderFieldMap.find(id);
209
if (itr == CLuceneIndexReaderFieldMap.end()) {
212
return itr->second.c_str();
217
CLuceneIndexReader::mapId(const char* id) {
218
wstring tid = utf8toucs2(id);
219
return mapId(tid.c_str());
224
CLuceneIndexReader::Private::createWildCardTerm(const wchar_t* name,
225
const string& value) {
226
wstring v = utf8toucs2(value);
227
return _CLNEW Term(name, v.c_str());
230
CLuceneIndexReader::Private::createTerm(const wchar_t* name,
231
const string& value) {
232
wstring v = utf8toucs2(value);
233
lucene::util::StringReader sr(v.c_str());
234
lucene::analysis::standard::StandardAnalyzer a;
235
lucene::analysis::TokenStream* ts = a.tokenStream(name, &sr);
236
lucene::analysis::Token* to = ts->next();
243
Term* t = _CLNEW Term(name, tv);
251
CLuceneIndexReader::Private::createKeywordTerm(const wchar_t* name,
252
const string& value) {
253
wstring v = utf8toucs2(value);
254
Term* t = _CLNEW Term(name, v.c_str());
258
CLuceneIndexReader::Private::createBooleanQuery(const Strigi::Query& query) {
259
BooleanQuery* bq = _CLNEW BooleanQuery();
260
bool isAnd = query.type() == Strigi::Query::And;
261
const vector<Strigi::Query>& sub = query.subQueries();
262
for (vector<Strigi::Query>::const_iterator i = sub.begin(); i != sub.end();
264
Query* q = createQuery(*i);
265
bq->add(q, true, isAnd, i->negate());
270
CLuceneIndexReader::Private::createQuery(const Strigi::Query& query) {
271
return query.subQueries().size()
272
? createBooleanQuery(query)
273
: createSimpleQuery(query);
276
CLuceneIndexReader::Private::createSimpleQuery(const Strigi::Query& query) {
277
switch (query.fields().size()) {
278
case 0: return createSingleFieldQuery(FieldRegister::contentFieldName,
279
query);//return createNoFieldQuery(query);
280
case 1: return createSingleFieldQuery(query.fields()[0], query);
281
default: return createMultiFieldQuery(query);
285
CLuceneIndexReader::Private::createSingleFieldQuery(const string& field,
286
const Strigi::Query& query) {
287
wstring fieldname = mapId(field.c_str());
290
const string& val = query.term().string();
291
switch (query.type()) {
292
case Strigi::Query::LessThan:
293
t = createTerm(fieldname.c_str(), val.c_str());
294
q = _CLNEW RangeQuery(0, t, false);
296
case Strigi::Query::LessThanEquals:
297
t = createTerm(fieldname.c_str(), query.term().string());
298
q = _CLNEW RangeQuery(0, t, true);
300
case Strigi::Query::GreaterThan:
301
t = createTerm(fieldname.c_str(), query.term().string());
302
q = _CLNEW RangeQuery(t, 0, false);
304
case Strigi::Query::GreaterThanEquals:
305
t = createTerm(fieldname.c_str(), query.term().string());
306
q = _CLNEW RangeQuery(t, 0, true);
308
case Strigi::Query::Keyword:
309
t = createKeywordTerm(fieldname.c_str(), query.term().string());
310
q = _CLNEW TermQuery(t);
312
case Strigi::Query::Contains:
313
t = createWildCardTerm(fieldname.c_str(), "*" + val + "*");
314
q = _CLNEW WildcardQuery(t);
316
case Strigi::Query::StartsWith:
317
t = createWildCardTerm(fieldname.c_str(), val + "*");
318
q = _CLNEW WildcardQuery(t);
320
case Strigi::Query::Equals:
322
if (strpbrk(val.c_str(), "*?")) {
323
t = createWildCardTerm(fieldname.c_str(), val);
324
q = _CLNEW WildcardQuery(t);
326
t = createTerm(fieldname.c_str(), val);
327
q = _CLNEW TermQuery(t);
334
CLuceneIndexReader::Private::createMultiFieldQuery(const Strigi::Query& query) {
335
BooleanQuery* bq = _CLNEW BooleanQuery();
336
for (vector<string>::const_iterator i = query.fields().begin();
337
i != query.fields().end(); ++i) {
338
Query* q = createSingleFieldQuery(*i, query);
339
bq->add(q, true, false, false);
344
CLuceneIndexReader::Private::createNoFieldQuery(const Strigi::Query& query) {
345
vector<string> fields = reader.fieldNames();
346
BooleanQuery* bq = _CLNEW BooleanQuery();
347
for (vector<string>::const_iterator i = fields.begin(); i != fields.end();
349
Query* q = createSingleFieldQuery(*i, query);
350
bq->add(q, true, false, false);
355
CLuceneIndexReader::Private::addField(lucene::document::Field* field,
356
IndexedDocument& doc) {
357
if (field->stringValue() == 0) return;
358
string value(wchartoutf8(field->stringValue()));
359
const TCHAR* name = field->name();
360
if (wcscmp(name, content()) == 0) {
361
doc.fragment = value;
362
} else if (wcscmp(name, systemlocation()) == 0) {
364
} else if (wcscmp(name, mimetype()) == 0) {
365
doc.mimetype = value;
366
} else if (wcscmp(name, mtime()) == 0) {
367
doc.mtime=atol(value.c_str());
368
} else if (wcscmp(name, size()) == 0) {
370
doc.size = atoi(size.c_str());
372
doc.properties.insert(make_pair<const string, string>(
373
wchartoutf8(name), value));
377
CLuceneIndexReader::Private::getFieldValue(lucene::document::Field* field,
378
Variant::Type type) const {
379
if (field->stringValue() == 0) return Variant();
380
Variant v(wchartoutf8(field->stringValue()));
381
if (type == Variant::b_val) {
383
} else if (type == Variant::i_val) {
385
} else if (type == Variant::as_val) {
391
CLuceneIndexReader::countHits(const Strigi::Query& q) {
392
if (!checkReader()) return -1;
393
// if the query is empty, we return the number of files in the index
394
if (q.term().string().size() == 0 && q.subQueries().size() == 0) {
395
return countDocuments();
398
Query* bq = p->createQuery(q);
402
IndexSearcher searcher(reader);
403
vector<IndexedDocument> results;
407
hits = searcher.search(bq);
409
} catch (CLuceneError& err) {
410
/* HitCounter counter;
411
QueryFilter* filter = _CLNEW QueryFilter(&bq);
413
BitSet* bits = filter->bits(reader);
414
int32_t n = bits->size();
415
for (int32_t i=0; i<n; ++i) {
416
if (bits->get(i)) s++;
418
} catch (CLuceneError& err2) {
419
printf("ccould not query: %s\n", err.what());
422
searcher._search(0, filter, &counter);
423
} catch (CLuceneError& err2) {
424
printf("ccould not query: %s\n", err.what());
428
printf("counted %i hits\n", count);
429
// try to do a constant score query
430
//QueryFilter* filter = _CLNEW QueryFilter(&bq);
431
ConstantScoreQuery csq(filter);*/
432
fprintf(stderr, "could not query: %s\n", err.what());
439
vector<IndexedDocument>
440
CLuceneIndexReader::query(const Strigi::Query& q, int off, int max) {
441
vector<IndexedDocument> results;
442
if (!checkReader()) {
445
// handle special commands
446
if (q.fields().size() == 1 && q.fields()[0].empty()
447
&& q.term().string().substr(0, 14) == "strigispecial:") {
448
return p->strigiSpecial(q.term().string());
451
Query* bq = p->createQuery(q);
452
IndexSearcher searcher(reader);
456
hits = searcher.search(bq);
458
} catch (CLuceneError& err) {
459
fprintf(stderr, "could not query: %s\n", err.what());
461
if (off < 0) off = 0;
463
if (max < 0) max = s;
464
if (max > s) max = s;
466
results.reserve(max-off);
468
for (int i = off; i < max; ++i) {
469
Document *d = &hits->doc(i);
471
doc.score = hits->score(i);
472
DocumentFieldEnumeration* e = d->fields();
473
while (e->hasMoreElements()) {
474
Field* f = e->nextElement();
475
Private::addField(f, doc);
477
results.push_back(doc);
488
CLuceneIndexReader::getDocuments(const std::vector<std::string>& fullFields,
489
const std::vector<Strigi::Variant::Type>& types,
490
std::vector<std::vector<Strigi::Variant> >& result, int off, int max) {
492
int maxDoc = reader->maxDoc();
493
for (int i=0; i<off; i++) {
494
while (pos < maxDoc && reader->isDeleted(pos)) pos++;
495
if (pos == maxDoc) return;
498
if (max < 0) max = 0;
500
Document* d = new Document();
501
for (int i = 0; i < max && pos < maxDoc; ++i) {
502
while (pos < maxDoc && reader->isDeleted(pos)) pos++;
504
if (pos == maxDoc || !reader->document(pos++, d)) {
508
vector<Variant>& doc = result[i];
510
doc.resize(fullFields.size());
512
DocumentFieldEnumeration* e = d->fields();
513
while (e->hasMoreElements()) {
514
Field* field = e->nextElement();
515
string name(wchartoutf8(field->name()));
516
for (uint j = 0; j < fullFields.size(); ++j) {
517
if (fullFields[j] == name) {
518
doc[j] = p->getFieldValue(field, types[j]);
527
CLuceneIndexReader::getHits(const Strigi::Query& q,
528
const std::vector<std::string>& fields,
529
const std::vector<Strigi::Variant::Type>& types,
530
std::vector<std::vector<Strigi::Variant> >& result, int off, int max) {
532
if (!checkReader() || types.size() < fields.size()) {
536
vector<string> fullFields;
537
fullFields.resize(fields.size());
538
for (size_t i = 0; i < fields.size(); i++) {
539
if (fields[i].compare(0, 6, "xesam:") == 0) {
540
fullFields[i].assign(
541
"http://freedesktop.org/standards/xesam/1.0/core#"
542
+ fields[i].substr(6));
543
} else if (fields[i].compare(0, 4, "nie:") == 0) {
544
fullFields[i].assign(
545
"http://www.semanticdesktop.org/ontologies/2007/01/19/nie#"
546
+ fields[i].substr(4));
548
fullFields[i].assign(fields[i]);
552
// if the query is empty, we return the number of files in the index
553
if (q.term().string().size() == 0 && q.subQueries().size() == 0) {
554
getDocuments(fullFields, types, result, off, max);
558
Query* bq = p->createQuery(q);
559
IndexSearcher searcher(reader);
563
hits = searcher.search(bq);
565
} catch (CLuceneError& err) {
566
fprintf(stderr, "could not query: %s\n", err.what());
568
if (off < 0) off = 0;
570
if (max < 0) max = s;
571
if (max > s) max = s;
573
result.reserve(max-off);
575
result.resize(max-off);
576
for (int i = off; i < max; ++i) {
577
Document *d = &hits->doc(i);
578
vector<Variant>& doc = result[i-off];
580
doc.resize(fields.size());
582
DocumentFieldEnumeration* e = d->fields();
583
while (e->hasMoreElements()) {
584
Field* field = e->nextElement();
585
string name(wchartoutf8(field->name()));
586
for (uint j = 0; j < fullFields.size(); ++j) {
587
if (fullFields[j] == name) {
588
doc[j] = p->getFieldValue(field, types[j]);
601
CLuceneIndexReader::countDocuments() {
602
if (!checkReader(true)) return -1;
603
if (doccount == -1) {
604
doccount = reader->numDocs();
609
CLuceneIndexReader::countWords() {
610
if (!checkReader()) return -1;
611
if (wordcount == -1) {
614
lucene::index::TermEnum *terms = reader->terms();
615
while (terms->next()) wordcount++;
622
CLuceneIndexReader::indexSize() {
623
return manager->indexSize();
626
CLuceneIndexReader::documentId(const string& uri) {
627
if (!checkReader()) return -1;
630
Term term(mapId(Private::systemlocation()), utf8toucs2( uri ).c_str());
631
TermDocs* docs = reader->termDocs(&term);
637
if (id != -1 && reader->isDeleted((int32_t)id)) {
644
* Retrieve the mtime of the document with id @docid. If this document
645
* is not in the index, the time 0 is returned.
648
CLuceneIndexReader::mTime(int64_t docid) {
649
if (docid < 0) return 0;
650
if (!checkReader(true)) return 0;
652
Document *d = reader->document((int32_t)docid);
654
const TCHAR* v = d->get(Private::mtime());
655
mtime = atoi(wchartoutf8( v ).c_str());
661
* Retrieve the mtime of the document with id @docid. If this document
662
* is not in the index, the time 0 is returned.
665
CLuceneIndexReader::mTime(const std::string& uri) {
666
return mTime(documentId(uri));
670
vector<pair<string,uint32_t> > h;
674
vector<pair<string,uint32_t> >
675
makeTimeHistogram(const vector<int>& v) {
676
map<int32_t, int32_t> m;
677
vector<int32_t>::const_iterator i;
679
for (i = v.begin(); i < v.end(); ++i) {
682
t = *localtime( &ti ); // is thread-safe on win32
684
localtime_r(&ti, &t);
686
int32_t c = 10000*t.tm_year + 100*t.tm_mon + t.tm_mday;
689
vector<pair<string,uint32_t> > h;
692
map<int32_t,int32_t>::const_iterator j;
693
for (j = m.begin(); j != m.end(); ++j) {
694
str << j->first + 19000100;
695
h.push_back(make_pair<string,uint32_t>(str.str(), j->second));
700
vector<pair<string,uint32_t> >
701
makeHistogram(const vector<int>& v, int min, int max) {
702
map<int32_t, int32_t> m;
703
vector<int32_t>::const_iterator i;
704
for (i = v.begin(); i < v.end(); ++i) {
707
vector<pair<string,uint32_t> > h;
710
map<int32_t,int32_t>::const_iterator j;
711
for (j = m.begin(); j != m.end(); ++j) {
713
h.push_back(make_pair<string,uint32_t>(str.str(), j->second));
718
vector<pair<string,uint32_t> >
719
CLuceneIndexReader::histogram(const string& query,
720
const string& fieldname, const string& labeltype) {
721
vector<pair<string,uint32_t> > h;
722
if (!checkReader()) {
725
Strigi::QueryParser parser;
726
Strigi::Query q = parser.buildQuery(query);
727
Query* bq = p->createQuery(q);
728
IndexSearcher searcher(reader);
732
hits = searcher.search(bq);
734
} catch (CLuceneError& err) {
735
fprintf(stderr, "could not query: %s\n", err.what());
737
wstring field = utf8toucs2(fieldname);
738
int32_t max = INT_MIN;
739
int32_t min = INT_MAX;
740
vector<int32_t> values;
743
for (int i = 0; i < s; ++i) {
744
Document *d = &hits->doc(i);
745
const TCHAR* v = d->get(field.c_str());
747
int val = (int)strtol(wchartoutf8( v ).c_str(), &end, 10);
752
values.push_back(val);
753
max = (max>val) ?max :val;
754
min = (min<val) ?min :val;
762
if (fieldname == FieldRegister::mtimeFieldName || labeltype == "time") {
763
return makeTimeHistogram(values);
765
return makeHistogram(values, min, max);
769
CLuceneIndexReader::fieldNames() {
771
if (!checkReader()) {
774
TCHAR** names = reader->getFieldNames();
775
if (names == 0) return s;
778
string str(wchartoutf8(*n));
783
_CLDELETE_ARRAY(names);
787
CLuceneIndexReader::countKeywords(const string& keywordprefix,
788
const vector<string>& fieldnames) {
792
CLuceneIndexReader::keywords(const string& keywordmatch,
793
const vector<string>& fieldnames, uint32_t max, uint32_t offset) {
795
if (fieldnames.size()) {
801
wstring prefix = utf8toucs2(keywordmatch);
802
const wchar_t* prefixtext = prefix.c_str();
803
string::size_type prefixLen = prefix.length();
804
vector<string>::const_iterator i;
806
for (i = fn.begin(); i != fn.end() && s.size() << max; ++i) {
807
wstring fieldname(utf8toucs2(*i));
808
Term term(fieldname.c_str(), prefix.c_str());
809
TermEnum* enumerator = reader->terms(&term);
811
lastTerm = enumerator->term(false);
813
if (prefixLen > lastTerm->textLength()
814
|| wcsncmp(lastTerm->text(), prefixtext, prefixLen)
818
s.insert(lastTerm->text());
820
} while (enumerator->next() && s.size() < max);
825
set<wstring>::const_iterator j;
826
for (j = s.begin(); j != s.end(); ++j) {
827
k.push_back(wchartoutf8(*j));
832
CLuceneIndexReader::getChildren(const std::string& parent,
833
std::map<std::string, time_t>& children) {
835
// force a fresh reader. This is important because the function
836
// getChildren is essential for updating the index
837
if ( !checkReader(true) ) {
841
Term* t = Private::createKeywordTerm(Private::parentlocation(),
843
Query* q = _CLNEW TermQuery(t);
845
IndexSearcher searcher(reader);
849
hits = searcher.search(q);
850
nhits = hits->length();
851
} catch (CLuceneError& err) {
852
fprintf(stderr, "could not query: %s\n", err.what());
854
const TCHAR* mtime = mapId(Private::mtime());
855
for (int i = 0; i < nhits; ++i) {
856
Document* d = &hits->doc(i);
858
const TCHAR* v = d->get(mtime);
859
// check that mtime is defined for this document
861
time_t mtime = atoi(wchartoutf8( v ).c_str());
862
v = d->get(Private::systemlocation());
864
children[wchartoutf8( v )] = mtime;
875
vector<IndexedDocument>
876
CLuceneIndexReader::Private::strigiSpecial(const string& command) {
877
vector<IndexedDocument> r;
878
cerr << "strigispecial " << command << endl;
879
// we are going to count the size of each of the fields in this index
880
// this requires that we loop through all fields
881
lucene::index::TermEnum* terms = reader.reader->terms();
883
map<const TCHAR *, int64_t> lengths;
884
while (terms->next()) {
885
lengths[terms->term()->field()] += terms->term()->textLength();
886
// cerr << wchartoutf8(terms->term()->field()) << '\t'
887
// << wchartoutf8(terms->term()->text()) << endl;
890
for (map<const TCHAR *, int64_t>::const_iterator i = lengths.begin();
891
i != lengths.end(); ++i) {
892
cerr << wchartoutf8(i->first) << '\t' << i->second << endl;
896
cerr << "total" << '\t' << total << endl;
898
int32_t max = reader.reader->numDocs();
899
for (int32_t i=0; i < max; ++i) {
900
lucene::document::Document* d = reader.reader->document(i);
901
lucene::document::DocumentFieldEnumeration* e = d->fields();
902
while (e->hasMoreElements()) {
903
Field* f = e->nextElement();
905
total += wcslen(f->stringValue());
910
cerr << "total" << '\t' << total << endl;