fixed my previous commit that broke help context/index parser :-(
[wxWidgets.git] / src / html / helpdata.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: helpdata.cpp
3 // Purpose: wxHtmlHelpData
4 // Notes: Based on htmlhelp.cpp, implementing a monolithic
5 // HTML Help controller class, by Vaclav Slavik
6 // Author: Harm van der Heijden and Vaclav Slavik
7 // RCS-ID: $Id$
8 // Copyright: (c) Harm van der Heijden and Vaclav Slavik
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 #ifdef __GNUG__
13 #pragma implementation
14 #endif
15
16 // For compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
18
19 #ifdef __BORLANDC__
20 #pragma hdrstop
21 #endif
22
23 #include "wx/defs.h"
24
25 #if wxUSE_HTML
26
27 #ifndef WXPRECOMP
28 #include "wx/wx.h"
29 #endif
30
31 #include "wx/html/helpdata.h"
32 #include "wx/tokenzr.h"
33 #include "wx/wfstream.h"
34 #include "wx/busyinfo.h"
35 #include "wx/html/htmlpars.h"
36 #include "wx/html/htmldefs.h"
37
38 #include "wx/arrimpl.cpp"
39 WX_DEFINE_OBJARRAY(wxHtmlBookRecArray)
40
41 //-----------------------------------------------------------------------------
42 // static helper functions
43 //-----------------------------------------------------------------------------
44
45 // Reads one line, stores it into buf and returns pointer to new line or NULL.
46 static char* ReadLine(char *line, char *buf)
47 {
48 char *writeptr = buf, *readptr = line;
49
50 while (*readptr != 0 && *readptr != '\r' && *readptr != '\n') *(writeptr++) = *(readptr++);
51 *writeptr = 0;
52 while (*readptr == '\r' || *readptr == '\n') readptr++;
53 if (*readptr == 0) return NULL;
54 else return readptr;
55 }
56
57
58 static wxString SafeFileName(const wxString& s)
59 {
60 wxString res = s;
61 res.Replace(wxT(":"), wxT("_"), TRUE);
62 res.Replace(wxT(" "), wxT("_"), TRUE);
63 res.Replace(wxT("/"), wxT("_"), TRUE);
64 res.Replace(wxT("\\"), wxT("_"), TRUE);
65 res.Replace(wxT("#"), wxT("_"), TRUE);
66 res.Replace(wxT("."), wxT("_"), TRUE);
67 return res;
68 }
69
70
71 static int LINKAGEMODE IndexCompareFunc(const void *a, const void *b)
72 {
73 return wxStrcmp(((wxHtmlContentsItem*)a) -> m_Name, ((wxHtmlContentsItem*)b) -> m_Name);
74 }
75
76
77 //-----------------------------------------------------------------------------
78 // HP_Parser
79 //-----------------------------------------------------------------------------
80
81 class HP_Parser : public wxHtmlParser
82 {
83 public:
84 void AddText(const char* WXUNUSED(text)) { }
85 wxObject* GetProduct() { return NULL; }
86 };
87
88
89 //-----------------------------------------------------------------------------
90 // HP_TagHandler
91 //-----------------------------------------------------------------------------
92
93 class HP_TagHandler : public wxHtmlTagHandler
94 {
95 private:
96 wxString m_Name, m_Page;
97 int m_Level;
98 int m_ID;
99 int m_Index;
100 wxHtmlContentsItem *m_Items;
101 int m_ItemsCnt;
102 wxHtmlBookRecord *m_Book;
103
104 public:
105 HP_TagHandler(wxHtmlBookRecord *b) : wxHtmlTagHandler() {m_Book = b; m_Items = NULL; m_ItemsCnt = 0; m_Name = m_Page = wxEmptyString; m_Level = 0; }
106 wxString GetSupportedTags() { return wxT("UL,OBJECT,PARAM"); }
107 bool HandleTag(const wxHtmlTag& tag);
108 void WriteOut(wxHtmlContentsItem*& array, int& size);
109 void ReadIn(wxHtmlContentsItem* array, int size);
110 };
111
112
113 bool HP_TagHandler::HandleTag(const wxHtmlTag& tag)
114 {
115 if (tag.GetName() == wxT("UL")) {
116 m_Level++;
117 ParseInner(tag);
118 m_Level--;
119 return TRUE;
120 }
121 else if (tag.GetName() == wxT("OBJECT")) {
122 m_Name = m_Page = wxEmptyString;
123 ParseInner(tag);
124
125 if (!m_Page.IsEmpty())
126 /* should be 'if (tag.GetParam("TYPE") == "text/sitemap")'
127 but this works fine. Valid HHW's file may contain only two
128 object tags:
129
130 <OBJECT type="text/site properties">
131 <param name="ImageType" value="Folder">
132 </OBJECT>
133
134 or
135
136 <OBJECT type="text/sitemap">
137 <param name="Name" value="main page">
138 <param name="Local" value="another.htm">
139 </OBJECT>
140
141 We're interested in the latter. !m_Page.IsEmpty() is valid
142 condition because text/site properties does not contain Local param
143 */
144 {
145 if (m_ItemsCnt % wxHTML_REALLOC_STEP == 0)
146 m_Items = (wxHtmlContentsItem*) realloc(m_Items, (m_ItemsCnt + wxHTML_REALLOC_STEP) * sizeof(wxHtmlContentsItem));
147 m_Items[m_ItemsCnt].m_Level = m_Level;
148 m_Items[m_ItemsCnt].m_ID = m_ID;
149 m_Items[m_ItemsCnt].m_Page = new wxChar[m_Page.Length() + 1];
150 wxStrcpy(m_Items[m_ItemsCnt].m_Page, m_Page.c_str());
151 m_Items[m_ItemsCnt].m_Name = new wxChar [m_Name.Length() + 1];
152 wxStrcpy(m_Items[m_ItemsCnt].m_Name, m_Name.c_str());
153 m_Items[m_ItemsCnt].m_Book = m_Book;
154 m_ItemsCnt++;
155 }
156
157 return TRUE;
158 }
159 else { // "PARAM"
160 if (m_Name == wxEmptyString && tag.GetParam(wxT("NAME")) == wxT("Name")) m_Name = tag.GetParam(wxT("VALUE"));
161 if (tag.GetParam(wxT("NAME")) == wxT("Local")) m_Page = tag.GetParam(wxT("VALUE"));
162 if (tag.GetParam(wxT("NAME")) == wxT("ID")) tag.ScanParam(wxT("VALUE"), wxT("%i"), &m_ID);
163 return FALSE;
164 }
165 }
166
167
168
169 void HP_TagHandler::WriteOut(wxHtmlContentsItem*& array, int& size)
170 {
171 array = m_Items;
172 size = m_ItemsCnt;
173 m_Items = NULL;
174 m_ItemsCnt = 0;
175 }
176
177 void HP_TagHandler::ReadIn(wxHtmlContentsItem* array, int size)
178 {
179 m_Items = array;
180 m_ItemsCnt = size;
181 }
182
183
184
185
186 //-----------------------------------------------------------------------------
187 // wxHtmlHelpData
188 //-----------------------------------------------------------------------------
189
190 IMPLEMENT_DYNAMIC_CLASS(wxHtmlHelpData, wxObject)
191
192 wxHtmlHelpData::wxHtmlHelpData()
193 {
194 m_TempPath = wxEmptyString;
195
196 m_Contents = NULL;
197 m_ContentsCnt = 0;
198 m_Index = NULL;
199 m_IndexCnt = 0;
200 }
201
202 wxHtmlHelpData::~wxHtmlHelpData()
203 {
204 int i;
205
206 m_BookRecords.Empty();
207 if (m_Contents) {
208 for (i = 0; i < m_ContentsCnt; i++) {
209 delete[] m_Contents[i].m_Page;
210 delete[] m_Contents[i].m_Name;
211 }
212 free(m_Contents);
213 }
214 if (m_Index) {
215 for (i = 0; i < m_IndexCnt; i++) {
216 delete[] m_Index[i].m_Page;
217 delete[] m_Index[i].m_Name;
218 }
219 free(m_Index);
220 }
221 }
222
223 bool wxHtmlHelpData::LoadMSProject(wxHtmlBookRecord *book, wxFileSystem& fsys, const wxString& indexfile, const wxString& contentsfile)
224 {
225 wxFSFile *f;
226 char *buf;
227 int sz;
228 wxString string;
229
230 HP_Parser parser;
231 HP_TagHandler *handler = new HP_TagHandler(book);
232 parser.AddTagHandler(handler);
233
234 f = ( contentsfile.IsEmpty() ? 0 : fsys.OpenFile(contentsfile) );
235 if (f) {
236 sz = f -> GetStream() -> GetSize();
237 buf = new char[sz + 1];
238 buf[sz] = 0;
239 f -> GetStream() -> Read(buf, sz);
240 delete f;
241 handler -> ReadIn(m_Contents, m_ContentsCnt);
242 parser.Parse(buf);
243 handler -> WriteOut(m_Contents, m_ContentsCnt);
244 delete[] buf;
245 }
246
247 f = ( indexfile.IsEmpty() ? 0 : fsys.OpenFile(indexfile) );
248 if (f) {
249 sz = f -> GetStream() -> GetSize();
250 buf = new char[sz + 1];
251 buf[sz] = 0;
252 f -> GetStream() -> Read(buf, sz);
253 delete f;
254 handler -> ReadIn(m_Index, m_IndexCnt);
255 parser.Parse(buf);
256 handler -> WriteOut(m_Index, m_IndexCnt);
257 delete[] buf;
258 }
259 return TRUE;
260 }
261
262
263 bool wxHtmlHelpData::LoadCachedBook(wxHtmlBookRecord *book, wxInputStream *f)
264 {
265 int i, st;
266 int x;
267
268 /* load contents : */
269
270 f -> Read(&x, sizeof(x));
271 st = m_ContentsCnt;
272 m_ContentsCnt += x;
273 m_Contents = (wxHtmlContentsItem*) realloc(m_Contents, (m_ContentsCnt / wxHTML_REALLOC_STEP + 1) * wxHTML_REALLOC_STEP * sizeof(wxHtmlContentsItem));
274 for (i = st; i < m_ContentsCnt; i++) {
275 f -> Read(&x, sizeof(x));
276 m_Contents[i].m_Level = x;
277 f -> Read(&x, sizeof(x));
278 m_Contents[i].m_ID = x;
279 f -> Read(&x, sizeof(x));
280 m_Contents[i].m_Name = new wxChar[x];
281 f -> Read(m_Contents[i].m_Name, x*sizeof(wxChar));
282 f -> Read(&x, sizeof(x));
283 m_Contents[i].m_Page = new wxChar[x];
284 f -> Read(m_Contents[i].m_Page, x*sizeof(wxChar));
285 m_Contents[i].m_Book = book;
286 }
287
288 /* load index : */
289
290 f -> Read(&x, sizeof(x));
291 st = m_IndexCnt;
292 m_IndexCnt += x;
293 m_Index = (wxHtmlContentsItem*) realloc(m_Index, (m_IndexCnt / wxHTML_REALLOC_STEP + 1) * wxHTML_REALLOC_STEP * sizeof(wxHtmlContentsItem));
294 for (i = st; i < m_IndexCnt; i++) {
295 f -> Read(&x, sizeof(x));
296 m_Index[i].m_Name = new wxChar[x];
297 f -> Read(m_Index[i].m_Name, x*sizeof(wxChar));
298 f -> Read(&x, sizeof(x));
299 m_Index[i].m_Page = new wxChar[x];
300 f -> Read(m_Index[i].m_Page, x*sizeof(wxChar));
301 m_Index[i].m_Book = book;
302 }
303 return TRUE;
304 }
305
306
307 bool wxHtmlHelpData::SaveCachedBook(wxHtmlBookRecord *book, wxOutputStream *f)
308 {
309 int i;
310 int x;
311
312 /* save contents : */
313
314 x = 0;
315 for (i = 0; i < m_ContentsCnt; i++) if (m_Contents[i].m_Book == book && m_Contents[i].m_Level > 0) x++;
316 f -> Write(&x, sizeof(x));
317 for (i = 0; i < m_ContentsCnt; i++) {
318 if (m_Contents[i].m_Book != book || m_Contents[i].m_Level == 0) continue;
319 x = m_Contents[i].m_Level;
320 f -> Write(&x, sizeof(x));
321 x = m_Contents[i].m_ID;
322 f -> Write(&x, sizeof(x));
323 x = wxStrlen(m_Contents[i].m_Name) + 1;
324 f -> Write(&x, sizeof(x));
325 f -> Write(m_Contents[i].m_Name, x*sizeof(wxChar));
326 x = wxStrlen(m_Contents[i].m_Page) + 1;
327 f -> Write(&x, sizeof(x));
328 f -> Write(m_Contents[i].m_Page, x*sizeof(wxChar));
329 }
330
331 /* save index : */
332
333 x = 0;
334 for (i = 0; i < m_IndexCnt; i++) if (m_Index[i].m_Book == book && m_Index[i].m_Level > 0) x++;
335 f -> Write(&x, sizeof(x));
336 for (i = 0; i < m_IndexCnt; i++) {
337 if (m_Index[i].m_Book != book || m_Index[i].m_Level == 0) continue;
338 x = wxStrlen(m_Index[i].m_Name) + 1;
339 f -> Write(&x, sizeof(x));
340 f -> Write(m_Index[i].m_Name, x*sizeof(wxChar));
341 x = wxStrlen(m_Index[i].m_Page) + 1;
342 f -> Write(&x, sizeof(x));
343 f -> Write(m_Index[i].m_Page, x*sizeof(wxChar));
344 }
345 return TRUE;
346 }
347
348
349 void wxHtmlHelpData::SetTempDir(const wxString& path)
350 {
351 if (path == wxEmptyString) m_TempPath = path;
352 else {
353 if (wxIsAbsolutePath(path)) m_TempPath = path;
354 else m_TempPath = wxGetCwd() + "/" + path;
355
356 if (m_TempPath[m_TempPath.Length() - 1] != '/')
357 m_TempPath << "/";
358 }
359 }
360
361
362 bool wxHtmlHelpData::AddBookParam(const wxString& title, const wxString& contfile,
363 const wxString& indexfile, const wxString& deftopic,
364 const wxString& path)
365 {
366 wxFileSystem fsys;
367 wxFSFile *fi;
368 wxHtmlBookRecord *bookr;
369 wxString safetitle;
370
371 if (! path.IsEmpty())
372 fsys.ChangePathTo(path, TRUE);
373
374 bookr = new wxHtmlBookRecord(fsys.GetPath(), title, deftopic);
375
376 if (m_ContentsCnt % wxHTML_REALLOC_STEP == 0)
377 m_Contents = (wxHtmlContentsItem*) realloc(m_Contents, (m_ContentsCnt + wxHTML_REALLOC_STEP) * sizeof(wxHtmlContentsItem));
378 m_Contents[m_ContentsCnt].m_Level = 0;
379 m_Contents[m_ContentsCnt].m_ID = 0;
380 m_Contents[m_ContentsCnt].m_Page = new wxChar[deftopic.Length() + 1];
381 wxStrcpy(m_Contents[m_ContentsCnt].m_Page, deftopic.c_str());
382 m_Contents[m_ContentsCnt].m_Name = new wxChar [title.Length() + 1];
383 wxStrcpy(m_Contents[m_ContentsCnt].m_Name, title.c_str());
384 m_Contents[m_ContentsCnt].m_Book = bookr;
385
386 // store the contents index for later
387 int cont_start = m_ContentsCnt++;
388
389 // Try to find cached binary versions:
390 safetitle = SafeFileName(title);
391 fi = fsys.OpenFile(safetitle + wxT(".cached"));
392 if (fi == NULL) fi = fsys.OpenFile(m_TempPath + safetitle + wxT(".cached"));
393 if ((fi == NULL) || (m_TempPath == wxEmptyString)) {
394 LoadMSProject(bookr, fsys, indexfile, contfile);
395 if (m_TempPath != wxEmptyString) {
396 wxFileOutputStream *outs = new wxFileOutputStream(m_TempPath + safetitle + wxT(".cached"));
397 SaveCachedBook(bookr, outs);
398 delete outs;
399 }
400 } else {
401 LoadCachedBook(bookr, fi -> GetStream());
402 delete fi;
403 }
404
405 // Now store the contents range
406 bookr->SetContentsRange(cont_start, m_ContentsCnt);
407
408 m_BookRecords.Add(bookr);
409 if (m_IndexCnt > 0)
410 qsort(m_Index, m_IndexCnt, sizeof(wxHtmlContentsItem), IndexCompareFunc);
411
412 return TRUE;
413 }
414
415
416 bool wxHtmlHelpData::AddBook(const wxString& book)
417 {
418 if (book.Right(4).Lower() == wxT(".zip") ||
419 book.Right(4).Lower() == wxT(".htb") /*html book*/)
420
421 {
422 wxFileSystem fsys;
423 wxString s;
424 bool rt = FALSE;
425
426 s = fsys.FindFirst(book + wxT("#zip:") + wxT("*.hhp"), wxFILE);
427 while (!s.IsEmpty())
428 {
429 if (AddBook(s)) rt = TRUE;
430 s = fsys.FindNext();
431 }
432
433 return rt;
434 }
435
436
437 else
438 {
439 wxFSFile *fi;
440 wxFileSystem fsys;
441 wxInputStream *s;
442 wxString bookFull;
443
444 int sz;
445 char *buff, *lineptr;
446 char linebuf[300];
447
448 wxString title = _("noname"),
449 safetitle,
450 start = wxEmptyString,
451 contents = wxEmptyString, index = wxEmptyString;
452
453 if (wxIsAbsolutePath(book)) bookFull = book;
454 else bookFull = wxGetCwd() + "/" + book;
455
456 fi = fsys.OpenFile(bookFull);
457 if (fi == NULL) return FALSE;
458 fsys.ChangePathTo(bookFull);
459 s = fi -> GetStream();
460 sz = s -> GetSize();
461 buff = new char[sz + 1];
462 buff[sz] = 0;
463 s -> Read(buff, sz);
464 lineptr = buff;
465 delete fi;
466
467 do {
468 lineptr = ReadLine(lineptr, linebuf);
469
470 if (strstr(linebuf, "Title=") == linebuf)
471 title = linebuf + strlen("Title=");
472 if (strstr(linebuf, "Default topic=") == linebuf)
473 start = linebuf + strlen("Default topic=");
474 if (strstr(linebuf, "Index file=") == linebuf)
475 index = linebuf + strlen("Index file=");
476 if (strstr(linebuf, "Contents file=") == linebuf)
477 contents = linebuf + strlen("Contents file=");
478 } while (lineptr != NULL);
479 delete[] buff;
480
481 return AddBookParam(title, contents, index, start, fsys.GetPath());
482 }
483 }
484
485 wxString wxHtmlHelpData::FindPageByName(const wxString& x)
486 {
487 int cnt;
488 int i;
489 wxFileSystem fsys;
490 wxFSFile *f;
491 wxString url(wxEmptyString);
492
493 /* 1. try to open given file: */
494
495 cnt = m_BookRecords.GetCount();
496 for (i = 0; i < cnt; i++) {
497 f = fsys.OpenFile(m_BookRecords[i].GetBasePath() + x);
498 if (f) {
499 url = m_BookRecords[i].GetBasePath() + x;
500 delete f;
501 return url;
502 }
503 }
504
505
506 /* 2. try to find a book: */
507
508 for (i = 0; i < cnt; i++) {
509 if (m_BookRecords[i].GetTitle() == x) {
510 url = m_BookRecords[i].GetBasePath() + m_BookRecords[i].GetStart();
511 return url;
512 }
513 }
514
515 /* 3. try to find in contents: */
516
517 cnt = m_ContentsCnt;
518 for (i = 0; i < cnt; i++) {
519 if (wxStrcmp(m_Contents[i].m_Name, x) == 0) {
520 url = m_Contents[i].m_Book -> GetBasePath() + m_Contents[i].m_Page;
521 return url;
522 }
523 }
524
525
526 /* 4. try to find in index: */
527
528 cnt = m_IndexCnt;
529 for (i = 0; i < cnt; i++) {
530 if (wxStrcmp(m_Index[i].m_Name, x) == 0) {
531 url = m_Index[i].m_Book -> GetBasePath() + m_Index[i].m_Page;
532 return url;
533 }
534 }
535
536 return url;
537 }
538
539 wxString wxHtmlHelpData::FindPageById(int id)
540 {
541 int i;
542 wxString url(wxEmptyString);
543
544 for (i = 0; i < m_ContentsCnt; i++) {
545 if (m_Contents[i].m_ID == id) {
546 url = m_Contents[i].m_Book -> GetBasePath() + m_Contents[i].m_Page;
547 return url;
548 }
549 }
550
551 return url;
552 }
553
554 //----------------------------------------------------------------------------------
555 // wxHtmlSearchStatus functions
556 //----------------------------------------------------------------------------------
557
558 wxHtmlSearchStatus::wxHtmlSearchStatus(wxHtmlHelpData* data, const wxString& keyword,
559 const wxString& book)
560 {
561 m_Data = data;
562 m_Keyword = keyword;
563 wxHtmlBookRecord* bookr = NULL;
564 if (book != wxEmptyString) {
565 // we have to search in a specific book. Find it first
566 int i, cnt = data->m_BookRecords.GetCount();
567 for (i = 0; i < cnt; i++)
568 if (data->m_BookRecords[i].GetTitle() == book) {
569 bookr = &(data->m_BookRecords[i]);
570 m_CurIndex = bookr->GetContentsStart();
571 m_MaxIndex = bookr->GetContentsEnd();
572 break;
573 }
574 // check; we won't crash if the book doesn't exist, but it's Bad Anyway.
575 wxASSERT(bookr);
576 }
577 if (! bookr) {
578 // no book specified; search all books
579 m_CurIndex = 0;
580 m_MaxIndex = m_Data->m_ContentsCnt;
581 }
582 m_Engine.LookFor(keyword);
583 m_Active = (m_CurIndex < m_MaxIndex);
584 m_LastPage = wxEmptyString;
585 }
586
587 bool wxHtmlSearchStatus::Search()
588 {
589 wxFileSystem fsys;
590 wxFSFile *file;
591 int i = m_CurIndex; // shortcut
592 bool found = FALSE;
593
594 if (!m_Active) {
595 // sanity check. Illegal use, but we'll try to prevent a crash anyway
596 #if !defined(__VISAGECPP__)
597 wxASSERT(0);
598 #else
599 wxASSERT(m_Active);
600 #endif
601 return FALSE;
602 }
603
604 m_ContentsItem = NULL;
605 m_Name = wxEmptyString;
606
607 file = fsys.OpenFile(m_Data->m_Contents[i].m_Book -> GetBasePath() +
608 m_Data->m_Contents[i].m_Page);
609 if (file) {
610 if (m_LastPage != file->GetLocation()) {
611 m_LastPage = file->GetLocation();
612 if (m_Engine.Scan(file -> GetStream())) {
613 m_Name = m_Data->m_Contents[i].m_Name;
614 m_ContentsItem = m_Data->m_Contents + i;
615 found = TRUE;
616 }
617 }
618 delete file;
619 }
620 m_Active = (++m_CurIndex < m_MaxIndex);
621 return found;
622 }
623
624
625
626
627
628
629
630
631 //--------------------------------------------------------------------------------
632 // wxSearchEngine
633 //--------------------------------------------------------------------------------
634
635 void wxSearchEngine::LookFor(const wxString& keyword)
636 {
637 if (m_Keyword) delete[] m_Keyword;
638 m_Keyword = new wxChar[keyword.Length() + 1];
639 wxStrcpy(m_Keyword, keyword.c_str());
640 for (int i = wxStrlen(m_Keyword) - 1; i >= 0; i--)
641 if ((m_Keyword[i] >= wxT('A')) && (m_Keyword[i] <= wxT('Z')))
642 m_Keyword[i] += wxT('a') - wxT('A');
643 }
644
645
646
647 bool wxSearchEngine::Scan(wxInputStream *stream)
648 {
649 wxASSERT_MSG(m_Keyword != NULL, wxT("wxSearchEngine::LookFor must be called before scanning!"));
650
651 int i, j;
652 int lng = stream ->GetSize();
653 int wrd = wxStrlen(m_Keyword);
654 bool found = FALSE;
655 char *buf = new char[lng + 1];
656 stream -> Read(buf, lng);
657 buf[lng] = 0;
658
659 for (i = 0; i < lng; i++)
660 if ((buf[i] >= 'A') && (buf[i] <= 'Z')) buf[i] += 'a' - 'A';
661
662 for (i = 0; i < lng - wrd; i++) {
663 j = 0;
664 while ((j < wrd) && (buf[i + j] == m_Keyword[j])) j++;
665 if (j == wrd) {found = TRUE; break; }
666 }
667
668 delete[] buf;
669 return found;
670 }
671
672
673
674
675 #endif