source: trunk/src/helpers/stringh.c@ 106

Last change on this file since 106 was 105, checked in by umoeller, 24 years ago

Misc changes.

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 83.4 KB
Line 
1
2/*
3 *@@sourcefile stringh.c:
4 * contains string/text helper functions. These are good for
5 * parsing/splitting strings and other stuff used throughout
6 * XWorkplace.
7 *
8 * Note that these functions are really a bunch of very mixed
9 * up string helpers, which you may or may not find helpful.
10 * If you're looking for string functions with memory
11 * management, look at xstring.c instead.
12 *
13 * Usage: All OS/2 programs.
14 *
15 * Function prefixes (new with V0.81):
16 * -- strh* string helper functions.
17 *
18 * Note: Version numbering in this file relates to XWorkplace version
19 * numbering.
20 *
21 *@@header "helpers\stringh.h"
22 */
23
24/*
25 * Copyright (C) 1997-2000 Ulrich M”ller.
26 * Parts Copyright (C) 1991-1999 iMatix Corporation.
27 * This file is part of the "XWorkplace helpers" source package.
28 * This is free software; you can redistribute it and/or modify
29 * it under the terms of the GNU General Public License as published
30 * by the Free Software Foundation, in version 2 as it comes in the
31 * "COPYING" file of the XWorkplace main distribution.
32 * This program is distributed in the hope that it will be useful,
33 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 * GNU General Public License for more details.
36 */
37
38#define OS2EMX_PLAIN_CHAR
39 // this is needed for "os2emx.h"; if this is defined,
40 // emx will define PSZ as _signed_ char, otherwise
41 // as unsigned char
42
43#define INCL_WINSHELLDATA
44#include <os2.h>
45
46#include <stdlib.h>
47#include <stdio.h>
48#include <string.h>
49#include <ctype.h>
50#include <math.h>
51
52#include "setup.h" // code generation and debugging options
53
54#define DONT_REPLACE_STRINGH_MALLOC
55#include "helpers\stringh.h"
56#include "helpers\xstring.h" // extended string helpers
57
58#pragma hdrstop
59
60/*
61 *@@category: Helpers\C helpers\String management
62 * See stringh.c and xstring.c.
63 */
64
65/*
66 *@@category: Helpers\C helpers\String management\C string helpers
67 * See stringh.c.
68 */
69
70/*
71 *@@ strhcpy:
72 * like strdup, but this one doesn't crash if string2 is NULL,
73 * but sets the first byte in string1 to \0 instead.
74 *
75 *@@added V0.9.14 (2001-08-01) [umoeller]
76 */
77
78PSZ strhcpy(PSZ string1, const char *string2)
79{
80 if (string2)
81 return (strcpy(string1, string2));
82
83 *string1 = '\0';
84 return (string1);
85}
86
87#ifdef __DEBUG_MALLOC_ENABLED__
88
89/*
90 *@@ strhdup:
91 * memory debug version of strhdup.
92 *
93 *@@added V0.9.0 [umoeller]
94 */
95
96PSZ strhdupDebug(const char *pszSource,
97 const char *pcszSourceFile,
98 unsigned long ulLine,
99 const char *pcszFunction)
100{
101 if (pszSource)
102 {
103 PSZ p = (PSZ)memdMalloc(strlen(pszSource) + 1,
104 pcszSourceFile,
105 ulLine,
106 pcszFunction);
107 strcpy(p, pszSource);
108 return (p);
109 }
110 else
111 return (0);
112}
113
114#endif // __DEBUG_MALLOC_ENABLED__
115
116/*
117 *@@ strhdup:
118 * like strdup, but this one doesn't crash if pszSource is NULL,
119 * but returns NULL also.
120 *
121 *@@added V0.9.0 [umoeller]
122 */
123
124PSZ strhdup(const char *pszSource)
125{
126 if (pszSource)
127 return (strdup(pszSource));
128 else
129 return (0);
130}
131
132/*
133 *@@ strhcmp:
134 * better strcmp. This doesn't crash if any of the
135 * string pointers are NULL, but returns a proper
136 * value then.
137 *
138 * Besides, this is guaranteed to only return -1, 0,
139 * or +1, while strcmp can return any positive or
140 * negative value. This is useful for tree comparison
141 * funcs.
142 *
143 *@@added V0.9.9 (2001-02-16) [umoeller]
144 */
145
146int strhcmp(const char *p1, const char *p2)
147{
148 if (p1 && p2)
149 {
150 int i = strcmp(p1, p2);
151 if (i < 0) return (-1);
152 if (i > 0) return (+1);
153 }
154 else if (p1)
155 // but p2 is NULL: p1 greater than p2 then
156 return (+1);
157 else if (p2)
158 // but p1 is NULL: p1 less than p2 then
159 return (-1);
160
161 // return 0 if strcmp returned 0 above or both strings are NULL
162 return (0);
163}
164
165/*
166 *@@ strhicmp:
167 * like strhcmp, but compares without respect
168 * to case.
169 *
170 *@@added V0.9.9 (2001-04-07) [umoeller]
171 */
172
173int strhicmp(const char *p1, const char *p2)
174{
175 if (p1 && p2)
176 {
177 int i = stricmp(p1, p2);
178 if (i < 0) return (-1);
179 if (i > 0) return (+1);
180 }
181 else if (p1)
182 // but p2 is NULL: p1 greater than p2 then
183 return (+1);
184 else if (p2)
185 // but p1 is NULL: p1 less than p2 then
186 return (-1);
187
188 // return 0 if strcmp returned 0 above or both strings are NULL
189 return (0);
190}
191
192/*
193 *@@ strhistr:
194 * like strstr, but case-insensitive.
195 *
196 *@@changed V0.9.0 [umoeller]: crashed if null pointers were passed, thanks Rdiger Ihle
197 */
198
199PSZ strhistr(const char *string1, const char *string2)
200{
201 PSZ prc = NULL;
202
203 if ((string1) && (string2))
204 {
205 PSZ pszSrchIn = strdup(string1);
206 PSZ pszSrchFor = strdup(string2);
207
208 if ((pszSrchIn) && (pszSrchFor))
209 {
210 strupr(pszSrchIn);
211 strupr(pszSrchFor);
212
213 prc = strstr(pszSrchIn, pszSrchFor);
214 if (prc)
215 {
216 // prc now has the first occurence of the string,
217 // but in pszSrchIn; we need to map this
218 // return value to the original string
219 prc = (prc-pszSrchIn) // offset in pszSrchIn
220 + (PSZ)string1;
221 }
222 }
223 if (pszSrchFor)
224 free(pszSrchFor);
225 if (pszSrchIn)
226 free(pszSrchIn);
227 }
228 return (prc);
229}
230
231/*
232 *@@ strhncpy0:
233 * like strncpy, but always appends a 0 character.
234 */
235
236ULONG strhncpy0(PSZ pszTarget,
237 const char *pszSource,
238 ULONG cbSource)
239{
240 ULONG ul = 0;
241 PSZ pTarget = pszTarget,
242 pSource = (PSZ)pszSource;
243
244 for (ul = 0; ul < cbSource; ul++)
245 if (*pSource)
246 *pTarget++ = *pSource++;
247 else
248 break;
249 *pTarget = 0;
250
251 return (ul);
252}
253
254/*
255 * strhCount:
256 * this counts the occurences of c in pszSearch.
257 */
258
259ULONG strhCount(const char *pszSearch,
260 CHAR c)
261{
262 PSZ p = (PSZ)pszSearch;
263 ULONG ulCount = 0;
264 while (TRUE)
265 {
266 p = strchr(p, c);
267 if (p)
268 {
269 ulCount++;
270 p++;
271 }
272 else
273 break;
274 }
275 return (ulCount);
276}
277
278/*
279 *@@ strhIsDecimal:
280 * returns TRUE if psz consists of decimal digits only.
281 */
282
283BOOL strhIsDecimal(PSZ psz)
284{
285 PSZ p = psz;
286 while (*p != 0)
287 {
288 if (isdigit(*p) == 0)
289 return (FALSE);
290 p++;
291 }
292
293 return (TRUE);
294}
295
296#ifdef __DEBUG_MALLOC_ENABLED__
297
298/*
299 *@@ strhSubstrDebug:
300 * memory debug version of strhSubstr.
301 *
302 *@@added V0.9.14 (2001-08-01) [umoeller]
303 */
304
305PSZ strhSubstrDebug(const char *pBegin, // in: first char
306 const char *pEnd, // in: last char (not included)
307 const char *pcszSourceFile,
308 unsigned long ulLine,
309 const char *pcszFunction)
310{
311 PSZ pszSubstr = NULL;
312
313 if (pEnd > pBegin) // V0.9.9 (2001-04-04) [umoeller]
314 {
315 ULONG cbSubstr = (pEnd - pBegin);
316 if (pszSubstr = (PSZ)memdMalloc(cbSubstr + 1,
317 pcszSourceFile,
318 ulLine,
319 pcszFunction))
320 {
321 // strhncpy0(pszSubstr, pBegin, cbSubstr);
322 memcpy(pszSubstr, pBegin, cbSubstr); // V0.9.9 (2001-04-04) [umoeller]
323 *(pszSubstr + cbSubstr) = '\0';
324 }
325 }
326
327 return (pszSubstr);
328}
329
330#endif // __DEBUG_MALLOC_ENABLED__
331
332/*
333 *@@ strhSubstr:
334 * this creates a new PSZ containing the string
335 * from pBegin to pEnd, excluding the pEnd character.
336 * The new string is null-terminated. The caller
337 * must free() the new string after use.
338 *
339 * Example:
340 + "1234567890"
341 + ^ ^
342 + p1 p2
343 + strhSubstr(p1, p2)
344 * would return a new string containing "2345678".
345 *
346 *@@changed V0.9.9 (2001-04-04) [umoeller]: fixed crashes with invalid pointers
347 *@@changed V0.9.9 (2001-04-04) [umoeller]: now using memcpy for speed
348 */
349
350PSZ strhSubstr(const char *pBegin, // in: first char
351 const char *pEnd) // in: last char (not included)
352{
353 PSZ pszSubstr = NULL;
354
355 if (pEnd > pBegin) // V0.9.9 (2001-04-04) [umoeller]
356 {
357 ULONG cbSubstr = (pEnd - pBegin);
358 if (pszSubstr = (PSZ)malloc(cbSubstr + 1))
359 {
360 memcpy(pszSubstr, pBegin, cbSubstr); // V0.9.9 (2001-04-04) [umoeller]
361 *(pszSubstr + cbSubstr) = '\0';
362 }
363 }
364
365 return (pszSubstr);
366}
367
368/*
369 *@@ strhExtract:
370 * searches pszBuf for the cOpen character and returns
371 * the data in between cOpen and cClose, excluding
372 * those two characters, in a newly allocated buffer
373 * which you must free() afterwards.
374 *
375 * Spaces and newlines/linefeeds are skipped.
376 *
377 * If the search was successful, the new buffer
378 * is returned and, if (ppEnd != NULL), *ppEnd points
379 * to the first character after the cClose character
380 * found in the buffer.
381 *
382 * If the search was not successful, NULL is
383 * returned, and *ppEnd is unchanged.
384 *
385 * If another cOpen character is found before
386 * cClose, matching cClose characters will be skipped.
387 * You can therefore nest the cOpen and cClose
388 * characters.
389 *
390 * This function ignores cOpen and cClose characters
391 * in C-style comments and strings surrounded by
392 * double quotes.
393 *
394 * Example:
395 + PSZ pszBuf = "KEYWORD { --blah-- } next",
396 + pEnd;
397 + strhExtract(pszBuf,
398 + '{', '}',
399 + &pEnd)
400 * would return a new buffer containing " --blah-- ",
401 * and ppEnd would afterwards point to the space
402 * before "next" in the static buffer.
403 *
404 *@@added V0.9.0 [umoeller]
405 */
406
407PSZ strhExtract(PSZ pszBuf, // in: search buffer
408 CHAR cOpen, // in: opening char
409 CHAR cClose, // in: closing char
410 PSZ *ppEnd) // out: if != NULL, receives first character after closing char
411{
412 PSZ pszReturn = NULL;
413
414 if (pszBuf)
415 {
416 PSZ pOpen = strchr(pszBuf, cOpen);
417 if (pOpen)
418 {
419 // opening char found:
420 // now go thru the whole rest of the buffer
421 PSZ p = pOpen+1;
422 LONG lLevel = 1; // if this goes 0, we're done
423 while (*p)
424 {
425 if (*p == cOpen)
426 lLevel++;
427 else if (*p == cClose)
428 {
429 lLevel--;
430 if (lLevel <= 0)
431 {
432 // matching closing bracket found:
433 // extract string
434 pszReturn = strhSubstr(pOpen+1, // after cOpen
435 p); // excluding cClose
436 if (ppEnd)
437 *ppEnd = p+1;
438 break; // while (*p)
439 }
440 }
441 else if (*p == '\"')
442 {
443 // beginning of string:
444 PSZ p2 = p+1;
445 // find end of string
446 while ((*p2) && (*p2 != '\"'))
447 p2++;
448
449 if (*p2 == '\"')
450 // closing quote found:
451 // search on after that
452 p = p2; // raised below
453 else
454 break; // while (*p)
455 }
456
457 p++;
458 }
459 }
460 }
461
462 return (pszReturn);
463}
464
465/*
466 *@@ strhQuote:
467 * similar to strhExtract, except that
468 * opening and closing chars are the same,
469 * and therefore no nesting is possible.
470 * Useful for extracting stuff between
471 * quotes.
472 *
473 *@@added V0.9.0 [umoeller]
474 */
475
476PSZ strhQuote(PSZ pszBuf,
477 CHAR cQuote,
478 PSZ *ppEnd)
479{
480 PSZ pszReturn = NULL,
481 p1 = NULL;
482 if ((p1 = strchr(pszBuf, cQuote)))
483 {
484 PSZ p2 = strchr(p1+1, cQuote);
485 if (p2)
486 {
487 pszReturn = strhSubstr(p1+1, p2);
488 if (ppEnd)
489 // store closing char
490 *ppEnd = p2 + 1;
491 }
492 }
493
494 return (pszReturn);
495}
496
497/*
498 *@@ strhStrip:
499 * removes all double spaces.
500 * This copies within the "psz" buffer.
501 * If any double spaces are found, the
502 * string will be shorter than before,
503 * but the buffer is _not_ reallocated,
504 * so there will be unused bytes at the
505 * end.
506 *
507 * Returns the number of spaces removed.
508 *
509 *@@added V0.9.0 [umoeller]
510 */
511
512ULONG strhStrip(PSZ psz) // in/out: string
513{
514 PSZ p;
515 ULONG cb = strlen(psz),
516 ulrc = 0;
517
518 for (p = psz; p < psz+cb; p++)
519 {
520 if ((*p == ' ') && (*(p+1) == ' '))
521 {
522 PSZ p2 = p;
523 while (*p2)
524 {
525 *p2 = *(p2+1);
526 p2++;
527 }
528 cb--;
529 p--;
530 ulrc++;
531 }
532 }
533 return (ulrc);
534}
535
536/*
537 *@@ strhins:
538 * this inserts one string into another.
539 *
540 * pszInsert is inserted into pszBuffer at offset
541 * ulInsertOfs (which counts from 0).
542 *
543 * A newly allocated string is returned. pszBuffer is
544 * not changed. The new string should be free()'d after
545 * use.
546 *
547 * Upon errors, NULL is returned.
548 *
549 *@@changed V0.9.0 [umoeller]: completely rewritten.
550 */
551
552PSZ strhins(const char *pcszBuffer,
553 ULONG ulInsertOfs,
554 const char *pcszInsert)
555{
556 PSZ pszNew = NULL;
557
558 if ((pcszBuffer) && (pcszInsert))
559 {
560 do {
561 ULONG cbBuffer = strlen(pcszBuffer);
562 ULONG cbInsert = strlen(pcszInsert);
563
564 // check string length
565 if (ulInsertOfs > cbBuffer + 1)
566 break; // do
567
568 // OK, let's go.
569 pszNew = (PSZ)malloc(cbBuffer + cbInsert + 1); // additional null terminator
570
571 // copy stuff before pInsertPos
572 memcpy(pszNew,
573 pcszBuffer,
574 ulInsertOfs);
575 // copy string to be inserted
576 memcpy(pszNew + ulInsertOfs,
577 pcszInsert,
578 cbInsert);
579 // copy stuff after pInsertPos
580 strcpy(pszNew + ulInsertOfs + cbInsert,
581 pcszBuffer + ulInsertOfs);
582 } while (FALSE);
583 }
584
585 return (pszNew);
586}
587
588/*
589 *@@ strhFindReplace:
590 * wrapper around xstrFindReplace to work with C strings.
591 * Note that *ppszBuf can get reallocated and must
592 * be free()'able.
593 *
594 * Repetitive use of this wrapper is not recommended
595 * because it is considerably slower than xstrFindReplace.
596 *
597 *@@added V0.9.6 (2000-11-01) [umoeller]
598 *@@changed V0.9.7 (2001-01-15) [umoeller]: renamed from strhrpl
599 */
600
601ULONG strhFindReplace(PSZ *ppszBuf, // in/out: string
602 PULONG pulOfs, // in: where to begin search (0 = start);
603 // out: ofs of first char after replacement string
604 const char *pcszSearch, // in: search string; cannot be NULL
605 const char *pcszReplace) // in: replacement string; cannot be NULL
606{
607 ULONG ulrc = 0;
608 XSTRING xstrBuf,
609 xstrFind,
610 xstrReplace;
611 size_t ShiftTable[256];
612 BOOL fRepeat = FALSE;
613 xstrInitSet(&xstrBuf, *ppszBuf);
614 // reallocated and returned, so we're safe
615 xstrInitSet(&xstrFind, (PSZ)pcszSearch);
616 xstrInitSet(&xstrReplace, (PSZ)pcszReplace);
617 // these two are never freed, so we're safe too
618
619 if ((ulrc = xstrFindReplace(&xstrBuf,
620 pulOfs,
621 &xstrFind,
622 &xstrReplace,
623 ShiftTable,
624 &fRepeat)))
625 // replaced:
626 *ppszBuf = xstrBuf.psz;
627
628 return (ulrc);
629}
630
631/*
632 * strhWords:
633 * returns the no. of words in "psz".
634 * A string is considered a "word" if
635 * it is surrounded by spaces only.
636 *
637 *@@added V0.9.0 [umoeller]
638 */
639
640ULONG strhWords(PSZ psz)
641{
642 PSZ p;
643 ULONG cb = strlen(psz),
644 ulWords = 0;
645 if (cb > 1)
646 {
647 ulWords = 1;
648 for (p = psz; p < psz+cb; p++)
649 if (*p == ' ')
650 ulWords++;
651 }
652 return (ulWords);
653}
654
655/*
656 *@@ strhThousandsULong:
657 * converts a ULONG into a decimal string, while
658 * inserting thousands separators into it. Specify
659 * the separator character in cThousands.
660 *
661 * Returns pszTarget so you can use it directly
662 * with sprintf and the "%s" flag.
663 *
664 * For cThousands, you should use the data in
665 * OS2.INI ("PM_National" application), which is
666 * always set according to the "Country" object.
667 * You can use prfhQueryCountrySettings to
668 * retrieve this setting.
669 *
670 * Use strhThousandsDouble for "double" values.
671 */
672
673PSZ strhThousandsULong(PSZ pszTarget, // out: decimal as string
674 ULONG ul, // in: decimal to convert
675 CHAR cThousands) // in: separator char (e.g. '.')
676{
677 USHORT ust, uss, usc;
678 CHAR szTemp[40];
679 sprintf(szTemp, "%lu", ul);
680
681 ust = 0;
682 usc = strlen(szTemp);
683 for (uss = 0; uss < usc; uss++)
684 {
685 if (uss)
686 if (((usc - uss) % 3) == 0)
687 {
688 pszTarget[ust] = cThousands;
689 ust++;
690 }
691 pszTarget[ust] = szTemp[uss];
692 ust++;
693 }
694 pszTarget[ust] = '\0';
695
696 return (pszTarget);
697}
698
699/*
700 *@@ strhThousandsDouble:
701 * like strhThousandsULong, but for a "double"
702 * value. Note that after-comma values are truncated.
703 */
704
705PSZ strhThousandsDouble(PSZ pszTarget, double dbl, CHAR cThousands)
706{
707 USHORT ust, uss, usc;
708 CHAR szTemp[40];
709 sprintf(szTemp, "%.0f", floor(dbl));
710
711 ust = 0;
712 usc = strlen(szTemp);
713 for (uss = 0; uss < usc; uss++)
714 {
715 if (uss)
716 if (((usc - uss) % 3) == 0)
717 {
718 pszTarget[ust] = cThousands;
719 ust++;
720 }
721 pszTarget[ust] = szTemp[uss];
722 ust++;
723 }
724 pszTarget[ust] = '\0';
725
726 return (pszTarget);
727}
728
729/*
730 *@@ strhVariableDouble:
731 * like strhThousandsULong, but for a "double" value, and
732 * with a variable number of decimal places depending on the
733 * size of the quantity.
734 *
735 *@@added V0.9.6 (2000-11-12) [pr]
736 */
737
738PSZ strhVariableDouble(PSZ pszTarget,
739 double dbl,
740 PSZ pszUnits,
741 CHAR cThousands)
742{
743 if (dbl < 100.0)
744 sprintf(pszTarget, "%.2f%s", dbl, pszUnits);
745 else
746 if (dbl < 1000.0)
747 sprintf(pszTarget, "%.1f%s", dbl, pszUnits);
748 else
749 strcat(strhThousandsDouble(pszTarget, dbl, cThousands),
750 pszUnits);
751
752 return(pszTarget);
753}
754
755/*
756 *@@ strhFileDate:
757 * converts file date data to a string (to pszBuf).
758 * You can pass any FDATE structure to this function,
759 * which are returned in those FILEFINDBUF* or
760 * FILESTATUS* structs by the Dos* functions.
761 *
762 * ulDateFormat is the PM setting for the date format,
763 * as set in the "Country" object, and can be queried using
764 + PrfQueryProfileInt(HINI_USER, "PM_National", "iDate", 0);
765 *
766 * meaning:
767 * -- 0 mm.dd.yyyy (English)
768 * -- 1 dd.mm.yyyy (e.g. German)
769 * -- 2 yyyy.mm.dd (Japanese, ISO)
770 * -- 3 yyyy.dd.mm
771 *
772 * cDateSep is used as a date separator (e.g. '.').
773 * This can be queried using:
774 + prfhQueryProfileChar(HINI_USER, "PM_National", "sDate", '/');
775 *
776 * Alternatively, you can query all the country settings
777 * at once using prfhQueryCountrySettings (prfh.c).
778 *
779 *@@changed V0.9.0 (99-11-07) [umoeller]: now calling strhDateTime
780 */
781
782VOID strhFileDate(PSZ pszBuf, // out: string returned
783 FDATE *pfDate, // in: date information
784 ULONG ulDateFormat, // in: date format (0-3)
785 CHAR cDateSep) // in: date separator (e.g. '.')
786{
787 DATETIME dt;
788 dt.day = pfDate->day;
789 dt.month = pfDate->month;
790 dt.year = pfDate->year + 1980;
791
792 strhDateTime(pszBuf,
793 NULL, // no time
794 &dt,
795 ulDateFormat,
796 cDateSep,
797 0, 0); // no time
798}
799
800/*
801 *@@ strhFileTime:
802 * converts file time data to a string (to pszBuf).
803 * You can pass any FTIME structure to this function,
804 * which are returned in those FILEFINDBUF* or
805 * FILESTATUS* structs by the Dos* functions.
806 *
807 * ulTimeFormat is the PM setting for the time format,
808 * as set in the "Country" object, and can be queried using
809 + PrfQueryProfileInt(HINI_USER, "PM_National", "iTime", 0);
810 * meaning:
811 * -- 0 12-hour clock
812 * -- >0 24-hour clock
813 *
814 * cDateSep is used as a time separator (e.g. ':').
815 * This can be queried using:
816 + prfhQueryProfileChar(HINI_USER, "PM_National", "sTime", ':');
817 *
818 * Alternatively, you can query all the country settings
819 * at once using prfhQueryCountrySettings (prfh.c).
820 *
821 *@@changed V0.8.5 (99-03-15) [umoeller]: fixed 12-hour crash
822 *@@changed V0.9.0 (99-11-07) [umoeller]: now calling strhDateTime
823 */
824
825VOID strhFileTime(PSZ pszBuf, // out: string returned
826 FTIME *pfTime, // in: time information
827 ULONG ulTimeFormat, // in: 24-hour time format (0 or 1)
828 CHAR cTimeSep) // in: time separator (e.g. ':')
829{
830 DATETIME dt;
831 dt.hours = pfTime->hours;
832 dt.minutes = pfTime->minutes;
833 dt.seconds = pfTime->twosecs * 2;
834
835 strhDateTime(NULL, // no date
836 pszBuf,
837 &dt,
838 0, 0, // no date
839 ulTimeFormat,
840 cTimeSep);
841}
842
843/*
844 *@@ strhDateTime:
845 * converts Control Program DATETIME info
846 * into two strings. See strhFileDate and strhFileTime
847 * for more detailed parameter descriptions.
848 *
849 *@@added V0.9.0 (99-11-07) [umoeller]
850 */
851
852VOID strhDateTime(PSZ pszDate, // out: date string returned (can be NULL)
853 PSZ pszTime, // out: time string returned (can be NULL)
854 DATETIME *pDateTime, // in: date/time information
855 ULONG ulDateFormat, // in: date format (0-3); see strhFileDate
856 CHAR cDateSep, // in: date separator (e.g. '.')
857 ULONG ulTimeFormat, // in: 24-hour time format (0 or 1); see strhFileTime
858 CHAR cTimeSep) // in: time separator (e.g. ':')
859{
860 if (pszDate)
861 {
862 switch (ulDateFormat)
863 {
864 case 0: // mm.dd.yyyy (English)
865 sprintf(pszDate, "%02d%c%02d%c%04d",
866 pDateTime->month,
867 cDateSep,
868 pDateTime->day,
869 cDateSep,
870 pDateTime->year);
871 break;
872
873 case 1: // dd.mm.yyyy (e.g. German)
874 sprintf(pszDate, "%02d%c%02d%c%04d",
875 pDateTime->day,
876 cDateSep,
877 pDateTime->month,
878 cDateSep,
879 pDateTime->year);
880 break;
881
882 case 2: // yyyy.mm.dd (Japanese)
883 sprintf(pszDate, "%04d%c%02d%c%02d",
884 pDateTime->year,
885 cDateSep,
886 pDateTime->month,
887 cDateSep,
888 pDateTime->day);
889 break;
890
891 default: // yyyy.dd.mm
892 sprintf(pszDate, "%04d%c%02d%c%02d",
893 pDateTime->year,
894 cDateSep,
895 pDateTime->day,
896 cDateSep,
897 pDateTime->month);
898 break;
899 }
900 }
901
902 if (pszTime)
903 {
904 if (ulTimeFormat == 0)
905 {
906 // for 12-hour clock, we need additional INI data
907 CHAR szAMPM[10] = "err";
908
909 if (pDateTime->hours > 12)
910 {
911 // > 12h: PM.
912
913 // Note: 12:xx noon is 12 AM, not PM (even though
914 // AM stands for "ante meridiam", but English is just
915 // not logical), so that's handled below.
916
917 PrfQueryProfileString(HINI_USER,
918 "PM_National",
919 "s2359", // key
920 "PM", // default
921 szAMPM, sizeof(szAMPM)-1);
922 sprintf(pszTime, "%02d%c%02d%c%02d %s",
923 // leave 12 == 12 (not 0)
924 pDateTime->hours % 12,
925 cTimeSep,
926 pDateTime->minutes,
927 cTimeSep,
928 pDateTime->seconds,
929 szAMPM);
930 }
931 else
932 {
933 // <= 12h: AM
934 PrfQueryProfileString(HINI_USER,
935 "PM_National",
936 "s1159", // key
937 "AM", // default
938 szAMPM, sizeof(szAMPM)-1);
939 sprintf(pszTime, "%02d%c%02d%c%02d %s",
940 pDateTime->hours,
941 cTimeSep,
942 pDateTime->minutes,
943 cTimeSep,
944 pDateTime->seconds,
945 szAMPM);
946 }
947 }
948 else
949 // 24-hour clock
950 sprintf(pszTime, "%02d%c%02d%c%02d",
951 pDateTime->hours,
952 cTimeSep,
953 pDateTime->minutes,
954 cTimeSep,
955 pDateTime->seconds);
956 }
957}
958
959/*
960 *@@ strhGetWord:
961 * finds word boundaries.
962 *
963 * *ppszStart is used as the beginning of the
964 * search.
965 *
966 * If a word is found, *ppszStart is set to
967 * the first character of the word which was
968 * found and *ppszEnd receives the address
969 * of the first character _after_ the word,
970 * which is probably a space or a \n or \r char.
971 * We then return TRUE.
972 *
973 * The search is stopped if a null character
974 * is found or pLimit is reached. In that case,
975 * FALSE is returned.
976 *
977 *@@added V0.9.1 (2000-02-13) [umoeller]
978 */
979
980BOOL strhGetWord(PSZ *ppszStart, // in: start of search,
981 // out: start of word (if TRUE is returned)
982 const char *pLimit, // in: ptr to last char after *ppszStart to be
983 // searched; if the word does not end before
984 // or with this char, FALSE is returned
985 const char *pcszBeginChars, // stringh.h defines STRH_BEGIN_CHARS
986 const char *pcszEndChars, // stringh.h defines STRH_END_CHARS
987 PSZ *ppszEnd) // out: first char _after_ word
988 // (if TRUE is returned)
989{
990 // characters after which a word can be started
991 // const char *pcszBeginChars = "\x0d\x0a ";
992 // const char *pcszEndChars = "\x0d\x0a /-";
993
994 PSZ pStart = *ppszStart;
995
996 // find start of word
997 while ( (pStart < (PSZ)pLimit)
998 && (strchr(pcszBeginChars, *pStart))
999 )
1000 // if char is a "before word" char: go for next
1001 pStart++;
1002
1003 if (pStart < (PSZ)pLimit)
1004 {
1005 // found a valid "word start" character
1006 // (which is not in pcszBeginChars):
1007
1008 // find end of word
1009 PSZ pEndOfWord = pStart;
1010 while ( (pEndOfWord <= (PSZ)pLimit)
1011 && (strchr(pcszEndChars, *pEndOfWord) == 0)
1012 )
1013 // if char is not an "end word" char: go for next
1014 pEndOfWord++;
1015
1016 if (pEndOfWord <= (PSZ)pLimit)
1017 {
1018 // whoa, got a word:
1019 *ppszStart = pStart;
1020 *ppszEnd = pEndOfWord;
1021 return (TRUE);
1022 }
1023 }
1024
1025 return (FALSE);
1026}
1027
1028/*
1029 *@@ strhIsWord:
1030 * returns TRUE if p points to a "word"
1031 * in pcszBuf.
1032 *
1033 * p is considered a word if the character _before_
1034 * it is in pcszBeginChars and the char _after_
1035 * it (i.e. *(p+cbSearch)) is in pcszEndChars.
1036 *
1037 *@@added V0.9.6 (2000-11-12) [umoeller]
1038 */
1039
1040BOOL strhIsWord(const char *pcszBuf,
1041 const char *p, // in: start of word
1042 ULONG cbSearch, // in: length of word
1043 const char *pcszBeginChars, // suggestion: "\x0d\x0a ()/\\-,."
1044 const char *pcszEndChars) // suggestion: "\x0d\x0a ()/\\-,.:;"
1045{
1046 BOOL fEndOK = FALSE;
1047
1048 // check previous char
1049 if ( (p == pcszBuf)
1050 || (strchr(pcszBeginChars, *(p-1)))
1051 )
1052 {
1053 // OK, valid begin char:
1054 // check end char
1055 CHAR cNextChar = *(p + cbSearch);
1056 if (cNextChar == 0)
1057 fEndOK = TRUE;
1058 else
1059 {
1060 char *pc = strchr(pcszEndChars, cNextChar);
1061 if (pc)
1062 // OK, is end char: avoid doubles of that char,
1063 // but allow spaces
1064 if ( (cNextChar+1 != *pc)
1065 || (cNextChar+1 == ' ')
1066 || (cNextChar+1 == 0)
1067 )
1068 fEndOK = TRUE;
1069 }
1070 }
1071
1072 return (fEndOK);
1073}
1074
1075/*
1076 *@@ strhFindWord:
1077 * searches for pszSearch in pszBuf, which is
1078 * returned if found (or NULL if not).
1079 *
1080 * As opposed to strstr, this finds pszSearch
1081 * only if it is a "word". A search string is
1082 * considered a word if the character _before_
1083 * it is in pcszBeginChars and the char _after_
1084 * it is in pcszEndChars.
1085 *
1086 * Example:
1087 + strhFindWord("This is an example.", "is");
1088 + returns ...........^ this, but not the "is" in "This".
1089 *
1090 * The algorithm here uses strstr to find pszSearch in pszBuf
1091 * and performs additional "is-word" checks for each item found
1092 * (by calling strhIsWord).
1093 *
1094 * Note that this function is fairly slow compared to xstrFindWord.
1095 *
1096 *@@added V0.9.0 (99-11-08) [umoeller]
1097 *@@changed V0.9.0 (99-11-10) [umoeller]: tried second algorithm, reverted to original...
1098 */
1099
1100PSZ strhFindWord(const char *pszBuf,
1101 const char *pszSearch,
1102 const char *pcszBeginChars, // suggestion: "\x0d\x0a ()/\\-,."
1103 const char *pcszEndChars) // suggestion: "\x0d\x0a ()/\\-,.:;"
1104{
1105 PSZ pszReturn = 0;
1106 ULONG cbBuf = strlen(pszBuf),
1107 cbSearch = strlen(pszSearch);
1108
1109 if ((cbBuf) && (cbSearch))
1110 {
1111 const char *p = pszBuf;
1112
1113 do // while p
1114 {
1115 p = strstr(p, pszSearch);
1116 if (p)
1117 {
1118 // string found:
1119 // check if that's a word
1120
1121 if (strhIsWord(pszBuf,
1122 p,
1123 cbSearch,
1124 pcszBeginChars,
1125 pcszEndChars))
1126 {
1127 // valid end char:
1128 pszReturn = (PSZ)p;
1129 break;
1130 }
1131
1132 p += cbSearch;
1133 }
1134 } while (p);
1135
1136 }
1137 return (pszReturn);
1138}
1139
1140/*
1141 *@@ strhFindEOL:
1142 * returns a pointer to the next \r, \n or null character
1143 * following pszSearchIn. Stores the offset in *pulOffset.
1144 *
1145 * This should never return NULL because at some point,
1146 * there will be a null byte in your string.
1147 *
1148 *@@added V0.9.4 (2000-07-01) [umoeller]
1149 */
1150
1151PSZ strhFindEOL(const char *pcszSearchIn, // in: where to search
1152 PULONG pulOffset) // out: offset (ptr can be NULL)
1153{
1154 const char *p = pcszSearchIn,
1155 *prc = 0;
1156 while (TRUE)
1157 {
1158 if ( (*p == '\r') || (*p == '\n') || (*p == 0) )
1159 {
1160 prc = p;
1161 break;
1162 }
1163 p++;
1164 }
1165
1166 if ((pulOffset) && (prc))
1167 *pulOffset = prc - pcszSearchIn;
1168
1169 return ((PSZ)prc);
1170}
1171
1172/*
1173 *@@ strhFindNextLine:
1174 * like strhFindEOL, but this returns the character
1175 * _after_ \r or \n. Note that this might return
1176 * a pointer to terminating NULL character also.
1177 */
1178
1179PSZ strhFindNextLine(PSZ pszSearchIn, PULONG pulOffset)
1180{
1181 PSZ pEOL = strhFindEOL(pszSearchIn, NULL);
1182 // pEOL now points to the \r char or the terminating 0 byte;
1183 // if not null byte, advance pointer
1184 PSZ pNextLine = pEOL;
1185 if (*pNextLine == '\r')
1186 pNextLine++;
1187 if (*pNextLine == '\n')
1188 pNextLine++;
1189 if (pulOffset)
1190 *pulOffset = pNextLine - pszSearchIn;
1191 return (pNextLine);
1192}
1193
1194/*
1195 *@@ strhBeautifyTitle:
1196 * replaces all line breaks (0xd, 0xa) with spaces.
1197 *
1198 *@@changed V0.9.12 (2001-05-17) [pr]: multiple line break chars. end up as only 1 space
1199 */
1200
1201BOOL strhBeautifyTitle(PSZ psz)
1202{
1203 BOOL rc = FALSE;
1204 CHAR *p = psz;
1205
1206 while(*p)
1207 if ( (*p == '\r')
1208 || (*p == '\n')
1209 )
1210 {
1211 rc = TRUE;
1212 if ( (p != psz)
1213 && (p[-1] == ' ')
1214 )
1215 memmove(p, p + 1, strlen(p));
1216 else
1217 *p++ = ' ';
1218 }
1219 else
1220 p++;
1221
1222 return (rc);
1223}
1224
1225/*
1226 * strhFindAttribValue:
1227 * searches for pszAttrib in pszSearchIn; if found,
1228 * returns the first character after the "=" char.
1229 * If "=" is not found, a space, \r, and \n are
1230 * also accepted. This function searches without
1231 * respecting case.
1232 *
1233 * <B>Example:</B>
1234 + strhFindAttribValue("<PAGE BLAH=\"data\">", "BLAH")
1235 +
1236 + returns ....................... ^ this address.
1237 *
1238 *@@added V0.9.0 [umoeller]
1239 *@@changed V0.9.3 (2000-05-19) [umoeller]: some speed optimizations
1240 *@@changed V0.9.12 (2001-05-22) [umoeller]: fixed space bug, thanks Yuri Dario
1241 */
1242
1243PSZ strhFindAttribValue(const char *pszSearchIn, const char *pszAttrib)
1244{
1245 PSZ prc = 0;
1246 PSZ pszSearchIn2, p;
1247 ULONG cbAttrib = strlen(pszAttrib),
1248 ulLength = strlen(pszSearchIn);
1249
1250 // use alloca(), so memory is freed on function exit
1251 pszSearchIn2 = (PSZ)alloca(ulLength + 1);
1252 memcpy(pszSearchIn2, pszSearchIn, ulLength + 1);
1253
1254 // 1) find token, (space char, \n, \r, \t)
1255 p = strtok(pszSearchIn2, " \n\r\t");
1256 while (p)
1257 {
1258 CHAR c2;
1259 PSZ pOrig;
1260
1261 // check tag name
1262 if (!strnicmp(p, pszAttrib, cbAttrib))
1263 {
1264 // position in original string
1265 pOrig = (PSZ)pszSearchIn + (p - pszSearchIn2);
1266
1267 // yes:
1268 prc = pOrig + cbAttrib;
1269 c2 = *prc;
1270 while ( ( (c2 == ' ')
1271 || (c2 == '=')
1272 || (c2 == '\n')
1273 || (c2 == '\r')
1274 )
1275 && (c2 != 0)
1276 )
1277 c2 = *++prc;
1278
1279 break;
1280 }
1281
1282 p = strtok(NULL, " \n\r\t");
1283 }
1284
1285 return (prc);
1286}
1287
1288/* PSZ strhFindAttribValue(const char *pszSearchIn, const char *pszAttrib)
1289{
1290 PSZ prc = 0;
1291 PSZ pszSearchIn2 = (PSZ)pszSearchIn,
1292 p,
1293 p2;
1294 ULONG cbAttrib = strlen(pszAttrib);
1295
1296 // 1) find space char
1297 while ((p = strchr(pszSearchIn2, ' ')))
1298 {
1299 CHAR c;
1300 p++;
1301 if (strlen(p) >= cbAttrib) // V0.9.9 (2001-03-27) [umoeller]
1302 {
1303 c = *(p+cbAttrib); // V0.9.3 (2000-05-19) [umoeller]
1304 // now check whether the p+strlen(pszAttrib)
1305 // is a valid end-of-tag character
1306 if ( (memicmp(p, (PVOID)pszAttrib, cbAttrib) == 0)
1307 && ( (c == ' ')
1308 || (c == '>')
1309 || (c == '=')
1310 || (c == '\r')
1311 || (c == '\n')
1312 || (c == 0)
1313 )
1314 )
1315 {
1316 // yes:
1317 CHAR c2;
1318 p2 = p + cbAttrib;
1319 c2 = *p2;
1320 while ( ( (c2 == ' ')
1321 || (c2 == '=')
1322 || (c2 == '\n')
1323 || (c2 == '\r')
1324 )
1325 && (c2 != 0)
1326 )
1327 c2 = *++p2;
1328
1329 prc = p2;
1330 break; // first while
1331 }
1332 }
1333 else
1334 break;
1335
1336 pszSearchIn2++;
1337 }
1338 return (prc);
1339} */
1340
1341/*
1342 * strhGetNumAttribValue:
1343 * stores the numerical parameter value of an HTML-style
1344 * tag in *pl.
1345 *
1346 * Returns the address of the tag parameter in the
1347 * search buffer, if found, or NULL.
1348 *
1349 * <B>Example:</B>
1350 + strhGetNumAttribValue("<PAGE BLAH=123>, "BLAH", &l);
1351 *
1352 * stores 123 in the "l" variable.
1353 *
1354 *@@added V0.9.0 [umoeller]
1355 *@@changed V0.9.9 (2001-04-04) [umoeller]: this failed on "123" strings in quotes, fixed
1356 */
1357
1358PSZ strhGetNumAttribValue(const char *pszSearchIn, // in: where to search
1359 const char *pszTag, // e.g. "INDEX"
1360 PLONG pl) // out: numerical value
1361{
1362 PSZ pParam;
1363 if ((pParam = strhFindAttribValue(pszSearchIn, pszTag)))
1364 {
1365 if ( (*pParam == '\"')
1366 || (*pParam == '\'')
1367 )
1368 pParam++; // V0.9.9 (2001-04-04) [umoeller]
1369
1370 sscanf(pParam, "%ld", pl);
1371 }
1372
1373 return (pParam);
1374}
1375
1376/*
1377 * strhGetTextAttr:
1378 * retrieves the attribute value of a textual HTML-style tag
1379 * in a newly allocated buffer, which is returned,
1380 * or NULL if attribute not found.
1381 * If an attribute value is to contain spaces, it
1382 * must be enclosed in quotes.
1383 *
1384 * The offset of the attribute data in pszSearchIn is
1385 * returned in *pulOffset so that you can do multiple
1386 * searches.
1387 *
1388 * This returns a new buffer, which should be free()'d after use.
1389 *
1390 * <B>Example:</B>
1391 + ULONG ulOfs = 0;
1392 + strhGetTextAttr("<PAGE BLAH="blublub">, "BLAH", &ulOfs)
1393 + ............^ ulOfs
1394 *
1395 * returns a new string with the value "blublub" (without
1396 * quotes) and sets ulOfs to 12.
1397 *
1398 *@@added V0.9.0 [umoeller]
1399 */
1400
1401PSZ strhGetTextAttr(const char *pszSearchIn,
1402 const char *pszTag,
1403 PULONG pulOffset) // out: offset where found
1404{
1405 PSZ pParam,
1406 pParam2,
1407 prc = NULL;
1408 ULONG ulCount = 0;
1409 LONG lNestingLevel = 0;
1410
1411 if ((pParam = strhFindAttribValue(pszSearchIn, pszTag)))
1412 {
1413 // determine end character to search for: a space
1414 CHAR cEnd = ' ';
1415 if (*pParam == '\"')
1416 {
1417 // or, if the data is enclosed in quotes, a quote
1418 cEnd = '\"';
1419 pParam++;
1420 }
1421
1422 if (pulOffset)
1423 // store the offset
1424 (*pulOffset) = pParam - (PSZ)pszSearchIn;
1425
1426 // now find end of attribute
1427 pParam2 = pParam;
1428 while (*pParam)
1429 {
1430 if (*pParam == cEnd)
1431 // end character found
1432 break;
1433 else if (*pParam == '<')
1434 // yet another opening tag found:
1435 // this is probably some "<" in the attributes
1436 lNestingLevel++;
1437 else if (*pParam == '>')
1438 {
1439 lNestingLevel--;
1440 if (lNestingLevel < 0)
1441 // end of tag found:
1442 break;
1443 }
1444 ulCount++;
1445 pParam++;
1446 }
1447
1448 // copy attribute to new buffer
1449 if (ulCount)
1450 {
1451 prc = (PSZ)malloc(ulCount+1);
1452 memcpy(prc, pParam2, ulCount);
1453 *(prc+ulCount) = 0;
1454 }
1455 }
1456 return (prc);
1457}
1458
1459/*
1460 * strhFindEndOfTag:
1461 * returns a pointer to the ">" char
1462 * which seems to terminate the tag beginning
1463 * after pszBeginOfTag.
1464 *
1465 * If additional "<" chars are found, we look
1466 * for additional ">" characters too.
1467 *
1468 * Note: You must pass the address of the opening
1469 * '<' character to this function.
1470 *
1471 * Example:
1472 + PSZ pszTest = "<BODY ATTR=\"<BODY>\">";
1473 + strhFindEndOfTag(pszTest)
1474 + returns.................................^ this.
1475 *
1476 *@@added V0.9.0 [umoeller]
1477 */
1478
1479PSZ strhFindEndOfTag(const char *pszBeginOfTag)
1480{
1481 PSZ p = (PSZ)pszBeginOfTag,
1482 prc = NULL;
1483 LONG lNestingLevel = 0;
1484
1485 while (*p)
1486 {
1487 if (*p == '<')
1488 // another opening tag found:
1489 lNestingLevel++;
1490 else if (*p == '>')
1491 {
1492 // closing tag found:
1493 lNestingLevel--;
1494 if (lNestingLevel < 1)
1495 {
1496 // corresponding: return this
1497 prc = p;
1498 break;
1499 }
1500 }
1501 p++;
1502 }
1503
1504 return (prc);
1505}
1506
1507/*
1508 * strhGetBlock:
1509 * this complex function searches the given string
1510 * for a pair of opening/closing HTML-style tags.
1511 *
1512 * If found, this routine returns TRUE and does
1513 * the following:
1514 *
1515 * 1) allocate a new buffer, copy the text
1516 * enclosed by the opening/closing tags
1517 * into it and set *ppszBlock to that
1518 * buffer;
1519 *
1520 * 2) if the opening tag has any attributes,
1521 * allocate another buffer, copy the
1522 * attributes into it and set *ppszAttrs
1523 * to that buffer; if no attributes are
1524 * found, *ppszAttrs will be NULL;
1525 *
1526 * 3) set *pulOffset to the offset from the
1527 * beginning of *ppszSearchIn where the
1528 * opening tag was found;
1529 *
1530 * 4) advance *ppszSearchIn to after the
1531 * closing tag, so that you can do
1532 * multiple searches without finding the
1533 * same tags twice.
1534 *
1535 * All buffers should be freed using free().
1536 *
1537 * This returns the following:
1538 * -- 0: no error
1539 * -- 1: tag not found at all (doesn't have to be an error)
1540 * -- 2: begin tag found, but no corresponding end tag found. This
1541 * is a real error.
1542 * -- 3: begin tag is not terminated by "&gt;" (e.g. "&lt;BEGINTAG whatever")
1543 *
1544 * <B>Example:</B>
1545 + PSZ pSearch = "&lt;PAGE INDEX=1&gt;This is page 1.&lt;/PAGE&gt;More text."
1546 + PSZ pszBlock, pszAttrs;
1547 + ULONG ulOfs;
1548 + strhGetBlock(&pSearch, "PAGE", &pszBlock, &pszAttrs, &ulOfs)
1549 *
1550 * would do the following:
1551 *
1552 * 1) set pszBlock to a new string containing "This is page 1."
1553 * without quotes;
1554 *
1555 * 2) set pszAttrs to a new string containing "&lt;PAGE INDEX=1&gt;";
1556 *
1557 * 3) set ulOfs to 0, because "&lt;PAGE" was found at the beginning;
1558 *
1559 * 4) pSearch would be advanced to point to the "More text"
1560 * string in the original buffer.
1561 *
1562 * Hey-hey. A one-shot function, fairly complicated, but indispensable
1563 * for HTML parsing.
1564 *
1565 *@@added V0.9.0 [umoeller]
1566 *@@changed V0.9.1 (2000-01-03) [umoeller]: fixed heap overwrites (thanks to string debugging)
1567 *@@changed V0.9.1 (2000-01-06) [umoeller]: changed prototype
1568 *@@changed V0.9.3 (2000-05-06) [umoeller]: NULL string check was missing
1569 */
1570
1571ULONG strhGetBlock(const char *pszSearchIn, // in: buffer to search
1572 PULONG pulSearchOffset, // in/out: offset where to start search (0 for beginning)
1573 PSZ pszTag,
1574 PSZ *ppszBlock, // out: block enclosed by the tags
1575 PSZ *ppszAttribs, // out: attributes of the opening tag
1576 PULONG pulOfsBeginTag, // out: offset from pszSearchIn where opening tag was found
1577 PULONG pulOfsBeginBlock) // out: offset from pszSearchIn where beginning of block was found
1578{
1579 ULONG ulrc = 1;
1580 PSZ pszBeginTag = (PSZ)pszSearchIn + *pulSearchOffset,
1581 pszSearch2 = pszBeginTag,
1582 pszClosingTag;
1583 ULONG cbTag = strlen(pszTag);
1584
1585 // go thru the block and check all tags if it's the
1586 // begin tag we're looking for
1587 while ((pszBeginTag = strchr(pszBeginTag, '<')))
1588 {
1589 if (memicmp(pszBeginTag+1, pszTag, strlen(pszTag)) == 0)
1590 // yes: stop
1591 break;
1592 else
1593 pszBeginTag++;
1594 }
1595
1596 if (pszBeginTag)
1597 {
1598 // we found <TAG>:
1599 ULONG ulNestingLevel = 0;
1600
1601 PSZ pszEndOfBeginTag = strhFindEndOfTag(pszBeginTag);
1602 // strchr(pszBeginTag, '>');
1603 if (pszEndOfBeginTag)
1604 {
1605 // does the caller want the attributes?
1606 if (ppszAttribs)
1607 {
1608 // yes: then copy them
1609 ULONG ulAttrLen = pszEndOfBeginTag - pszBeginTag;
1610 PSZ pszAttrs = (PSZ)malloc(ulAttrLen + 1);
1611 strncpy(pszAttrs, pszBeginTag, ulAttrLen);
1612 // add terminating 0
1613 *(pszAttrs + ulAttrLen) = 0;
1614
1615 *ppszAttribs = pszAttrs;
1616 }
1617
1618 // output offset of where we found the begin tag
1619 if (pulOfsBeginTag)
1620 *pulOfsBeginTag = pszBeginTag - (PSZ)pszSearchIn;
1621
1622 // now find corresponding closing tag (e.g. "</BODY>"
1623 pszBeginTag = pszEndOfBeginTag+1;
1624 // now we're behind the '>' char of the opening tag
1625 // increase offset of that too
1626 if (pulOfsBeginBlock)
1627 *pulOfsBeginBlock = pszBeginTag - (PSZ)pszSearchIn;
1628
1629 // find next closing tag;
1630 // for the first run, pszSearch2 points to right
1631 // after the '>' char of the opening tag
1632 pszSearch2 = pszBeginTag;
1633 while ( (pszSearch2) // fixed V0.9.3 (2000-05-06) [umoeller]
1634 && (pszClosingTag = strstr(pszSearch2, "<"))
1635 )
1636 {
1637 // if we have another opening tag before our closing
1638 // tag, we need to have several closing tags before
1639 // we're done
1640 if (memicmp(pszClosingTag+1, pszTag, cbTag) == 0)
1641 ulNestingLevel++;
1642 else
1643 {
1644 // is this ours?
1645 if ( (*(pszClosingTag+1) == '/')
1646 && (memicmp(pszClosingTag+2, pszTag, cbTag) == 0)
1647 )
1648 {
1649 // we've found a matching closing tag; is
1650 // it ours?
1651 if (ulNestingLevel == 0)
1652 {
1653 // our closing tag found:
1654 // allocate mem for a new buffer
1655 // and extract all the text between
1656 // open and closing tags to it
1657 ULONG ulLen = pszClosingTag - pszBeginTag;
1658 if (ppszBlock)
1659 {
1660 PSZ pNew = (PSZ)malloc(ulLen + 1);
1661 strhncpy0(pNew, pszBeginTag, ulLen);
1662 *ppszBlock = pNew;
1663 }
1664
1665 // raise search offset to after the closing tag
1666 *pulSearchOffset = (pszClosingTag + cbTag + 1) - (PSZ)pszSearchIn;
1667
1668 ulrc = 0;
1669
1670 break;
1671 } else
1672 // not our closing tag:
1673 ulNestingLevel--;
1674 }
1675 }
1676 // no matching closing tag: search on after that
1677 pszSearch2 = strhFindEndOfTag(pszClosingTag);
1678 } // end while (pszClosingTag = strstr(pszSearch2, "<"))
1679
1680 if (!pszClosingTag)
1681 // no matching closing tag found:
1682 // return 2 (closing tag not found)
1683 ulrc = 2;
1684 } // end if (pszBeginTag)
1685 else
1686 // no matching ">" for opening tag found:
1687 ulrc = 3;
1688 }
1689
1690 return (ulrc);
1691}
1692
1693/* ******************************************************************
1694 *
1695 * Miscellaneous
1696 *
1697 ********************************************************************/
1698
1699/*
1700 *@@ strhArrayAppend:
1701 * this appends a string to a "string array".
1702 *
1703 * A string array is considered a sequence of
1704 * zero-terminated strings in memory. That is,
1705 * after each string's null-byte, the next
1706 * string comes up.
1707 *
1708 * This is useful for composing a single block
1709 * of memory from, say, list box entries, which
1710 * can then be written to OS2.INI in one flush.
1711 *
1712 * To append strings to such an array, call this
1713 * function for each string you wish to append.
1714 * This will re-allocate *ppszRoot with each call,
1715 * and update *pcbRoot, which then contains the
1716 * total size of all strings (including all null
1717 * terminators).
1718 *
1719 * Pass *pcbRoot to PrfSaveProfileData to have the
1720 * block saved.
1721 *
1722 * Note: On the first call, *ppszRoot and *pcbRoot
1723 * _must_ be both NULL, or this crashes.
1724 *
1725 *@@changed V0.9.13 (2001-06-21) [umoeller]: added cbNew
1726 */
1727
1728VOID strhArrayAppend(PSZ *ppszRoot, // in: root of array
1729 const char *pcszNew, // in: string to append
1730 ULONG cbNew, // in: size of that string or 0 to run strlen() here
1731 PULONG pcbRoot) // in/out: size of array
1732{
1733 PSZ pszTemp;
1734
1735 if (!cbNew) // V0.9.13 (2001-06-21) [umoeller]
1736 cbNew = strlen(pcszNew);
1737
1738 pszTemp = (PSZ)malloc(*pcbRoot
1739 + cbNew
1740 + 1); // two null bytes
1741 if (*ppszRoot)
1742 {
1743 // not first loop: copy old stuff
1744 memcpy(pszTemp,
1745 *ppszRoot,
1746 *pcbRoot);
1747 free(*ppszRoot);
1748 }
1749 // append new string
1750 strcpy(pszTemp + *pcbRoot,
1751 pcszNew);
1752 // update root
1753 *ppszRoot = pszTemp;
1754 // update length
1755 *pcbRoot += cbNew + 1;
1756}
1757
1758/*
1759 *@@ strhCreateDump:
1760 * this dumps a memory block into a string
1761 * and returns that string in a new buffer.
1762 *
1763 * You must free() the returned PSZ after use.
1764 *
1765 * The output looks like the following:
1766 *
1767 + 0000: FE FF 0E 02 90 00 00 00 ........
1768 + 0008: FD 01 00 00 57 50 46 6F ....WPFo
1769 + 0010: 6C 64 65 72 00 78 01 34 lder.x.4
1770 *
1771 * Each line is terminated with a newline (\n)
1772 * character only.
1773 *
1774 *@@added V0.9.1 (2000-01-22) [umoeller]
1775 */
1776
1777PSZ strhCreateDump(PBYTE pb, // in: start address of buffer
1778 ULONG ulSize, // in: size of buffer
1779 ULONG ulIndent) // in: indentation of every line
1780{
1781 PSZ pszReturn = 0;
1782 XSTRING strReturn;
1783 CHAR szTemp[1000];
1784
1785 PBYTE pbCurrent = pb; // current byte
1786 ULONG ulCount = 0,
1787 ulCharsInLine = 0; // if this grows > 7, a new line is started
1788 CHAR szLine[400] = "",
1789 szAscii[30] = " "; // ASCII representation; filled for every line
1790 PSZ pszLine = szLine,
1791 pszAscii = szAscii;
1792
1793 xstrInit(&strReturn, (ulSize * 30) + ulIndent);
1794
1795 for (pbCurrent = pb;
1796 ulCount < ulSize;
1797 pbCurrent++, ulCount++)
1798 {
1799 if (ulCharsInLine == 0)
1800 {
1801 memset(szLine, ' ', ulIndent);
1802 pszLine += ulIndent;
1803 }
1804 pszLine += sprintf(pszLine, "%02lX ", (ULONG)*pbCurrent);
1805
1806 if ( (*pbCurrent > 31) && (*pbCurrent < 127) )
1807 // printable character:
1808 *pszAscii = *pbCurrent;
1809 else
1810 *pszAscii = '.';
1811 pszAscii++;
1812
1813 ulCharsInLine++;
1814 if ( (ulCharsInLine > 7) // 8 bytes added?
1815 || (ulCount == ulSize-1) // end of buffer reached?
1816 )
1817 {
1818 // if we haven't had eight bytes yet,
1819 // fill buffer up to eight bytes with spaces
1820 ULONG ul2;
1821 for (ul2 = ulCharsInLine;
1822 ul2 < 8;
1823 ul2++)
1824 pszLine += sprintf(pszLine, " ");
1825
1826 sprintf(szTemp, "%04lX: %s %s\n",
1827 (ulCount & 0xFFFFFFF8), // offset in hex
1828 szLine, // bytes string
1829 szAscii); // ASCII string
1830 xstrcat(&strReturn, szTemp, 0);
1831
1832 // restart line buffer
1833 pszLine = szLine;
1834
1835 // clear ASCII buffer
1836 strcpy(szAscii, " ");
1837 pszAscii = szAscii;
1838
1839 // reset line counter
1840 ulCharsInLine = 0;
1841 }
1842 }
1843
1844 if (strReturn.cbAllocated)
1845 pszReturn = strReturn.psz;
1846
1847 return (pszReturn);
1848}
1849
1850/* ******************************************************************
1851 *
1852 * Wildcard matching
1853 *
1854 ********************************************************************/
1855
1856/*
1857 * The following code has been taken from "fnmatch.zip".
1858 *
1859 * (c) 1994-1996 by Eberhard Mattes.
1860 */
1861
1862/* In OS/2 and DOS styles, both / and \ separate components of a path.
1863 * This macro returns true iff C is a separator. */
1864
1865#define IS_OS2_COMP_SEP(C) ((C) == '/' || (C) == '\\')
1866
1867
1868/* This macro returns true if C is at the end of a component of a
1869 * path. */
1870
1871#define IS_OS2_COMP_END(C) ((C) == 0 || IS_OS2_COMP_SEP (C))
1872
1873/*
1874 * skip_comp_os2:
1875 * Return a pointer to the next component of the path SRC, for OS/2
1876 * and DOS styles. When the end of the string is reached, a pointer
1877 * to the terminating null character is returned.
1878 *
1879 * (c) 1994-1996 by Eberhard Mattes.
1880 */
1881
1882static const unsigned char* skip_comp_os2(const unsigned char *src)
1883{
1884 /* Skip characters until hitting a separator or the end of the
1885 * string. */
1886
1887 while (!IS_OS2_COMP_END(*src))
1888 ++src;
1889
1890 /* Skip the separator if we hit a separator. */
1891
1892 if (*src != 0)
1893 ++src;
1894 return src;
1895}
1896
1897/*
1898 * has_colon:
1899 * returns true iff the path P contains a colon.
1900 *
1901 * (c) 1994-1996 by Eberhard Mattes.
1902 */
1903
1904static int has_colon(const unsigned char *p)
1905{
1906 while (*p != 0)
1907 if (*p == ':')
1908 return 1;
1909 else
1910 ++p;
1911 return 0;
1912}
1913
1914/*
1915 * match_comp_os2:
1916 * compares a single component (directory name or file name)
1917 * of the paths, for OS/2 and DOS styles. MASK and NAME point
1918 * into a component of the wildcard and the name to be checked,
1919 * respectively. Comparing stops at the next separator.
1920 * The FLAGS argument is the same as that of fnmatch().
1921 *
1922 * HAS_DOT is true if a dot is in the current component of NAME.
1923 * The number of dots is not restricted, even in DOS style.
1924 *
1925 * Returns FNM_MATCH iff MASK and NAME match.
1926 *
1927 * Note that this function is recursive.
1928 *
1929 * (c) 1994-1996 by Eberhard Mattes.
1930 */
1931
1932static int match_comp_os2(const unsigned char *mask,
1933 const unsigned char *name,
1934 unsigned flags,
1935 int has_dot)
1936{
1937 int rc;
1938
1939 for (;;)
1940 switch (*mask)
1941 {
1942 case 0:
1943
1944 /* There must be no extra characters at the end of NAME when
1945 * reaching the end of MASK unless _FNM_PATHPREFIX is set:
1946 * in that case, NAME may point to a separator. */
1947
1948 if (*name == 0)
1949 return FNM_MATCH;
1950 if ((flags & FNM_PATHPREFIX) && IS_OS2_COMP_SEP(*name))
1951 return FNM_MATCH;
1952 return FNM_NOMATCH;
1953
1954 case '/':
1955 case '\\':
1956
1957 /* Separators match separators. */
1958
1959 if (IS_OS2_COMP_SEP(*name))
1960 return FNM_MATCH;
1961
1962 /* If _FNM_PATHPREFIX is set, a trailing separator in MASK
1963 * is ignored at the end of NAME. */
1964
1965 if ((flags & FNM_PATHPREFIX) && mask[1] == 0 && *name == 0)
1966 return FNM_MATCH;
1967
1968 /* Stop comparing at the separator. */
1969
1970 return FNM_NOMATCH;
1971
1972 case '?':
1973
1974 /* A question mark matches one character. It does not match
1975 * a dot. At the end of the component (and before a dot),
1976 * it also matches zero characters. */
1977
1978 if (*name != '.' && !IS_OS2_COMP_END(*name))
1979 ++name;
1980 ++mask;
1981 break;
1982
1983 case '*':
1984
1985 /* An asterisk matches zero or more characters. In DOS
1986 * mode, dots are not matched. */
1987
1988 do
1989 {
1990 ++mask;
1991 }
1992 while (*mask == '*');
1993 for (;;)
1994 {
1995 rc = match_comp_os2(mask, name, flags, has_dot);
1996 if (rc != FNM_NOMATCH)
1997 return rc;
1998 if (IS_OS2_COMP_END(*name))
1999 return FNM_NOMATCH;
2000 if (*name == '.' && (flags & FNM_STYLE_MASK) == FNM_DOS)
2001 return FNM_NOMATCH;
2002 ++name;
2003 }
2004
2005 case '.':
2006
2007 /* A dot matches a dot. It also matches the implicit dot at
2008 * the end of a dot-less NAME. */
2009
2010 ++mask;
2011 if (*name == '.')
2012 ++name;
2013 else if (has_dot || !IS_OS2_COMP_END(*name))
2014 return FNM_NOMATCH;
2015 break;
2016
2017 default:
2018
2019 /* All other characters match themselves. */
2020
2021 if (flags & FNM_IGNORECASE)
2022 {
2023 if (tolower(*mask) != tolower(*name))
2024 return FNM_NOMATCH;
2025 }
2026 else
2027 {
2028 if (*mask != *name)
2029 return FNM_NOMATCH;
2030 }
2031 ++mask;
2032 ++name;
2033 break;
2034 }
2035}
2036
2037/*
2038 * match_comp:
2039 * compares a single component (directory name or file
2040 * name) of the paths, for all styles which need
2041 * component-by-component matching. MASK and NAME point
2042 * to the start of a component of the wildcard and the
2043 * name to be checked, respectively. Comparing stops at
2044 * the next separator. The FLAGS argument is the same as
2045 * that of fnmatch().
2046 *
2047 * Return FNM_MATCH iff MASK and NAME match.
2048 *
2049 * (c) 1994-1996 by Eberhard Mattes.
2050 */
2051
2052static int match_comp(const unsigned char *mask,
2053 const unsigned char *name,
2054 unsigned flags)
2055{
2056 const unsigned char *s;
2057
2058 switch (flags & FNM_STYLE_MASK)
2059 {
2060 case FNM_OS2:
2061 case FNM_DOS:
2062
2063 /* For OS/2 and DOS styles, we add an implicit dot at the end of
2064 * the component if the component doesn't include a dot. */
2065
2066 s = name;
2067 while (!IS_OS2_COMP_END(*s) && *s != '.')
2068 ++s;
2069 return match_comp_os2(mask, name, flags, *s == '.');
2070
2071 default:
2072 return FNM_ERR;
2073 }
2074}
2075
2076/* In Unix styles, / separates components of a path. This macro
2077 * returns true iff C is a separator. */
2078
2079#define IS_UNIX_COMP_SEP(C) ((C) == '/')
2080
2081
2082/* This macro returns true if C is at the end of a component of a
2083 * path. */
2084
2085#define IS_UNIX_COMP_END(C) ((C) == 0 || IS_UNIX_COMP_SEP (C))
2086
2087/*
2088 * match_unix:
2089 * matches complete paths for Unix styles.
2090 *
2091 * The FLAGS argument is the same as that of fnmatch().
2092 * COMP points to the start of the current component in
2093 * NAME. Return FNM_MATCH iff MASK and NAME match. The
2094 * backslash character is used for escaping ? and * unless
2095 * FNM_NOESCAPE is set.
2096 *
2097 * (c) 1994-1996 by Eberhard Mattes.
2098 */
2099
2100static int match_unix(const unsigned char *mask,
2101 const unsigned char *name,
2102 unsigned flags,
2103 const unsigned char *comp)
2104{
2105 unsigned char c1, c2;
2106 char invert, matched;
2107 const unsigned char *start;
2108 int rc;
2109
2110 for (;;)
2111 switch (*mask)
2112 {
2113 case 0:
2114
2115 /* There must be no extra characters at the end of NAME when
2116 * reaching the end of MASK unless _FNM_PATHPREFIX is set:
2117 * in that case, NAME may point to a separator. */
2118
2119 if (*name == 0)
2120 return FNM_MATCH;
2121 if ((flags & FNM_PATHPREFIX) && IS_UNIX_COMP_SEP(*name))
2122 return FNM_MATCH;
2123 return FNM_NOMATCH;
2124
2125 case '?':
2126
2127 /* A question mark matches one character. It does not match
2128 * the component separator if FNM_PATHNAME is set. It does
2129 * not match a dot at the start of a component if FNM_PERIOD
2130 * is set. */
2131
2132 if (*name == 0)
2133 return FNM_NOMATCH;
2134 if ((flags & FNM_PATHNAME) && IS_UNIX_COMP_SEP(*name))
2135 return FNM_NOMATCH;
2136 if (*name == '.' && (flags & FNM_PERIOD) && name == comp)
2137 return FNM_NOMATCH;
2138 ++mask;
2139 ++name;
2140 break;
2141
2142 case '*':
2143
2144 /* An asterisk matches zero or more characters. It does not
2145 * match the component separator if FNM_PATHNAME is set. It
2146 * does not match a dot at the start of a component if
2147 * FNM_PERIOD is set. */
2148
2149 if (*name == '.' && (flags & FNM_PERIOD) && name == comp)
2150 return FNM_NOMATCH;
2151 do
2152 {
2153 ++mask;
2154 }
2155 while (*mask == '*');
2156 for (;;)
2157 {
2158 rc = match_unix(mask, name, flags, comp);
2159 if (rc != FNM_NOMATCH)
2160 return rc;
2161 if (*name == 0)
2162 return FNM_NOMATCH;
2163 if ((flags & FNM_PATHNAME) && IS_UNIX_COMP_SEP(*name))
2164 return FNM_NOMATCH;
2165 ++name;
2166 }
2167
2168 case '/':
2169
2170 /* Separators match only separators. If _FNM_PATHPREFIX is
2171 * set, a trailing separator in MASK is ignored at the end
2172 * of NAME. */
2173
2174 if (!(IS_UNIX_COMP_SEP(*name)
2175 || ((flags & FNM_PATHPREFIX) && *name == 0
2176 && (mask[1] == 0
2177 || (!(flags & FNM_NOESCAPE) && mask[1] == '\\'
2178 && mask[2] == 0)))))
2179 return FNM_NOMATCH;
2180
2181 ++mask;
2182 if (*name != 0)
2183 ++name;
2184
2185 /* This is the beginning of a new component if FNM_PATHNAME
2186 * is set. */
2187
2188 if (flags & FNM_PATHNAME)
2189 comp = name;
2190 break;
2191
2192 case '[':
2193
2194 /* A set of characters. Always case-sensitive. */
2195
2196 if (*name == 0)
2197 return FNM_NOMATCH;
2198 if ((flags & FNM_PATHNAME) && IS_UNIX_COMP_SEP(*name))
2199 return FNM_NOMATCH;
2200 if (*name == '.' && (flags & FNM_PERIOD) && name == comp)
2201 return FNM_NOMATCH;
2202
2203 invert = 0;
2204 matched = 0;
2205 ++mask;
2206
2207 /* If the first character is a ! or ^, the set matches all
2208 * characters not listed in the set. */
2209
2210 if (*mask == '!' || *mask == '^')
2211 {
2212 ++mask;
2213 invert = 1;
2214 }
2215
2216 /* Loop over all the characters of the set. The loop ends
2217 * if the end of the string is reached or if a ] is
2218 * encountered unless it directly follows the initial [ or
2219 * [-. */
2220
2221 start = mask;
2222 while (!(*mask == 0 || (*mask == ']' && mask != start)))
2223 {
2224 /* Get the next character which is optionally preceded
2225 * by a backslash. */
2226
2227 c1 = *mask++;
2228 if (!(flags & FNM_NOESCAPE) && c1 == '\\')
2229 {
2230 if (*mask == 0)
2231 break;
2232 c1 = *mask++;
2233 }
2234
2235 /* Ranges of characters are written as a-z. Don't
2236 * forget to check for the end of the string and to
2237 * handle the backslash. If the character after - is a
2238 * ], it isn't a range. */
2239
2240 if (*mask == '-' && mask[1] != ']')
2241 {
2242 ++mask; /* Skip the - character */
2243 if (!(flags & FNM_NOESCAPE) && *mask == '\\')
2244 ++mask;
2245 if (*mask == 0)
2246 break;
2247 c2 = *mask++;
2248 }
2249 else
2250 c2 = c1;
2251
2252 /* Now check whether this character or range matches NAME. */
2253
2254 if (c1 <= *name && *name <= c2)
2255 matched = 1;
2256 }
2257
2258 /* If the end of the string is reached before a ] is found,
2259 * back up to the [ and compare it to NAME. */
2260
2261 if (*mask == 0)
2262 {
2263 if (*name != '[')
2264 return FNM_NOMATCH;
2265 ++name;
2266 mask = start;
2267 if (invert)
2268 --mask;
2269 }
2270 else
2271 {
2272 if (invert)
2273 matched = !matched;
2274 if (!matched)
2275 return FNM_NOMATCH;
2276 ++mask; /* Skip the ] character */
2277 if (*name != 0)
2278 ++name;
2279 }
2280 break;
2281
2282 case '\\':
2283 ++mask;
2284 if (flags & FNM_NOESCAPE)
2285 {
2286 if (*name != '\\')
2287 return FNM_NOMATCH;
2288 ++name;
2289 }
2290 else if (*mask == '*' || *mask == '?')
2291 {
2292 if (*mask != *name)
2293 return FNM_NOMATCH;
2294 ++mask;
2295 ++name;
2296 }
2297 break;
2298
2299 default:
2300
2301 /* All other characters match themselves. */
2302
2303 if (flags & FNM_IGNORECASE)
2304 {
2305 if (tolower(*mask) != tolower(*name))
2306 return FNM_NOMATCH;
2307 }
2308 else
2309 {
2310 if (*mask != *name)
2311 return FNM_NOMATCH;
2312 }
2313 ++mask;
2314 ++name;
2315 break;
2316 }
2317}
2318
2319/*
2320 * _fnmatch_unsigned:
2321 * Check whether the path name NAME matches the wildcard MASK.
2322 *
2323 * Return:
2324 * -- 0 (FNM_MATCH) if it matches,
2325 * -- _FNM_NOMATCH if it doesn't,
2326 * -- FNM_ERR on error.
2327 *
2328 * The operation of this function is controlled by FLAGS.
2329 * This is an internal function, with unsigned arguments.
2330 *
2331 * (c) 1994-1996 by Eberhard Mattes.
2332 */
2333
2334static int _fnmatch_unsigned(const unsigned char *mask,
2335 const unsigned char *name,
2336 unsigned flags)
2337{
2338 int m_drive,
2339 n_drive,
2340 rc;
2341
2342 /* Match and skip the drive name if present. */
2343
2344 m_drive = ((isalpha(mask[0]) && mask[1] == ':') ? mask[0] : -1);
2345 n_drive = ((isalpha(name[0]) && name[1] == ':') ? name[0] : -1);
2346
2347 if (m_drive != n_drive)
2348 {
2349 if (m_drive == -1 || n_drive == -1)
2350 return FNM_NOMATCH;
2351 if (!(flags & FNM_IGNORECASE))
2352 return FNM_NOMATCH;
2353 if (tolower(m_drive) != tolower(n_drive))
2354 return FNM_NOMATCH;
2355 }
2356
2357 if (m_drive != -1)
2358 mask += 2;
2359 if (n_drive != -1)
2360 name += 2;
2361
2362 /* Colons are not allowed in path names, except for the drive name,
2363 * which was skipped above. */
2364
2365 if (has_colon(mask) || has_colon(name))
2366 return FNM_ERR;
2367
2368 /* The name "\\server\path" should not be matched by mask
2369 * "\*\server\path". Ditto for /. */
2370
2371 switch (flags & FNM_STYLE_MASK)
2372 {
2373 case FNM_OS2:
2374 case FNM_DOS:
2375
2376 if (IS_OS2_COMP_SEP(name[0]) && IS_OS2_COMP_SEP(name[1]))
2377 {
2378 if (!(IS_OS2_COMP_SEP(mask[0]) && IS_OS2_COMP_SEP(mask[1])))
2379 return FNM_NOMATCH;
2380 name += 2;
2381 mask += 2;
2382 }
2383 break;
2384
2385 case FNM_POSIX:
2386
2387 if (name[0] == '/' && name[1] == '/')
2388 {
2389 int i;
2390
2391 name += 2;
2392 for (i = 0; i < 2; ++i)
2393 if (mask[0] == '/')
2394 ++mask;
2395 else if (mask[0] == '\\' && mask[1] == '/')
2396 mask += 2;
2397 else
2398 return FNM_NOMATCH;
2399 }
2400
2401 /* In Unix styles, treating ? and * w.r.t. components is simple.
2402 * No need to do matching component by component. */
2403
2404 return match_unix(mask, name, flags, name);
2405 }
2406
2407 /* Now compare all the components of the path name, one by one.
2408 * Note that the path separator must not be enclosed in brackets. */
2409
2410 while (*mask != 0 || *name != 0)
2411 {
2412
2413 /* If _FNM_PATHPREFIX is set, the names match if the end of MASK
2414 * is reached even if there are components left in NAME. */
2415
2416 if (*mask == 0 && (flags & FNM_PATHPREFIX))
2417 return FNM_MATCH;
2418
2419 /* Compare a single component of the path name. */
2420
2421 rc = match_comp(mask, name, flags);
2422 if (rc != FNM_MATCH)
2423 return rc;
2424
2425 /* Skip to the next component or to the end of the path name. */
2426
2427 mask = skip_comp_os2(mask);
2428 name = skip_comp_os2(name);
2429 }
2430
2431 /* If we reached the ends of both strings, the names match. */
2432
2433 if (*mask == 0 && *name == 0)
2434 return FNM_MATCH;
2435
2436 /* The names do not match. */
2437
2438 return FNM_NOMATCH;
2439}
2440
2441/*
2442 *@@ strhMatchOS2:
2443 * this matches wildcards, similar to what DosEditName does.
2444 * However, this does not require a file to be present, but
2445 * works on strings only.
2446 */
2447
2448BOOL strhMatchOS2(const char *pcszMask, // in: mask (e.g. "*.txt")
2449 const char *pcszName) // in: string to check (e.g. "test.txt")
2450{
2451 return ((BOOL)(_fnmatch_unsigned((const unsigned char *)pcszMask,
2452 (const unsigned char *)pcszName,
2453 FNM_OS2 | FNM_IGNORECASE)
2454 == FNM_MATCH)
2455 );
2456}
2457
2458/*
2459 *@@ strhMatchExt:
2460 * like strhMatchOS2, but this takes all the flags
2461 * for input.
2462 *
2463 *@@added V0.9.15 (2001-09-14) [umoeller]
2464 */
2465
2466BOOL strhMatchExt(const char *pcszMask, // in: mask (e.g. "*.txt")
2467 const char *pcszName, // in: string to check (e.g. "test.txt")
2468 unsigned flags) // in: FNM_* flags
2469{
2470 return ((BOOL)(_fnmatch_unsigned((const unsigned char *)pcszMask,
2471 (const unsigned char *)pcszName,
2472 flags)
2473 == FNM_MATCH)
2474 );
2475}
2476
2477/* ******************************************************************
2478 *
2479 * Fast string searches
2480 *
2481 ********************************************************************/
2482
2483#define ASSERT(a)
2484
2485/*
2486 * The following code has been taken from the "Standard
2487 * Function Library", file sflfind.c, and only slightly
2488 * modified to conform to the rest of this file.
2489 *
2490 * Written: 96/04/24 iMatix SFL project team <sfl@imatix.com>
2491 * Revised: 98/05/04
2492 *
2493 * Copyright: Copyright (c) 1991-99 iMatix Corporation.
2494 *
2495 * The SFL Licence allows incorporating SFL code into other
2496 * programs, as long as the copyright is reprinted and the
2497 * code is marked as modified, so this is what we do.
2498 */
2499
2500/*
2501 *@@ strhmemfind:
2502 * searches for a pattern in a block of memory using the
2503 * Boyer-Moore-Horspool-Sunday algorithm.
2504 *
2505 * The block and pattern may contain any values; you must
2506 * explicitly provide their lengths. If you search for strings,
2507 * use strlen() on the buffers.
2508 *
2509 * Returns a pointer to the pattern if found within the block,
2510 * or NULL if the pattern was not found.
2511 *
2512 * This algorithm needs a "shift table" to cache data for the
2513 * search pattern. This table can be reused when performing
2514 * several searches with the same pattern.
2515 *
2516 * "shift" must point to an array big enough to hold 256 (8**2)
2517 * "size_t" values.
2518 *
2519 * If (*repeat_find == FALSE), the shift table is initialized.
2520 * So on the first search with a given pattern, *repeat_find
2521 * should be FALSE. This function sets it to TRUE after the
2522 * shift table is initialised, allowing the initialisation
2523 * phase to be skipped on subsequent searches.
2524 *
2525 * This function is most effective when repeated searches are
2526 * made for the same pattern in one or more large buffers.
2527 *
2528 * Example:
2529 *
2530 + PSZ pszHaystack = "This is a sample string.",
2531 + pszNeedle = "string";
2532 + size_t shift[256];
2533 + BOOL fRepeat = FALSE;
2534 +
2535 + PSZ pFound = strhmemfind(pszHaystack,
2536 + strlen(pszHaystack), // block size
2537 + pszNeedle,
2538 + strlen(pszNeedle), // pattern size
2539 + shift,
2540 + &fRepeat);
2541 *
2542 * Taken from the "Standard Function Library", file sflfind.c.
2543 * Copyright: Copyright (c) 1991-99 iMatix Corporation.
2544 * Slightly modified by umoeller.
2545 *
2546 *@@added V0.9.3 (2000-05-08) [umoeller]
2547 */
2548
2549void* strhmemfind(const void *in_block, // in: block containing data
2550 size_t block_size, // in: size of block in bytes
2551 const void *in_pattern, // in: pattern to search for
2552 size_t pattern_size, // in: size of pattern block
2553 size_t *shift, // in/out: shift table (search buffer)
2554 BOOL *repeat_find) // in/out: if TRUE, *shift is already initialized
2555{
2556 size_t byte_nbr, // Distance through block
2557 match_size; // Size of matched part
2558 const unsigned char
2559 *match_base = NULL, // Base of match of pattern
2560 *match_ptr = NULL, // Point within current match
2561 *limit = NULL; // Last potiental match point
2562 const unsigned char
2563 *block = (unsigned char *) in_block, // Concrete pointer to block data
2564 *pattern = (unsigned char *) in_pattern; // Concrete pointer to search value
2565
2566 if ( (block == NULL)
2567 || (pattern == NULL)
2568 || (shift == NULL)
2569 )
2570 return (NULL);
2571
2572 // Pattern must be smaller or equal in size to string
2573 if (block_size < pattern_size)
2574 return (NULL); // Otherwise it's not found
2575
2576 if (pattern_size == 0) // Empty patterns match at start
2577 return ((void *)block);
2578
2579 // Build the shift table unless we're continuing a previous search
2580
2581 // The shift table determines how far to shift before trying to match
2582 // again, if a match at this point fails. If the byte after where the
2583 // end of our pattern falls is not in our pattern, then we start to
2584 // match again after that byte; otherwise we line up the last occurence
2585 // of that byte in our pattern under that byte, and try match again.
2586
2587 if (!repeat_find || !*repeat_find)
2588 {
2589 for (byte_nbr = 0;
2590 byte_nbr < 256;
2591 byte_nbr++)
2592 shift[byte_nbr] = pattern_size + 1;
2593 for (byte_nbr = 0;
2594 byte_nbr < pattern_size;
2595 byte_nbr++)
2596 shift[(unsigned char)pattern[byte_nbr]] = pattern_size - byte_nbr;
2597
2598 if (repeat_find)
2599 *repeat_find = TRUE;
2600 }
2601
2602 // Search for the block, each time jumping up by the amount
2603 // computed in the shift table
2604
2605 limit = block + (block_size - pattern_size + 1);
2606 ASSERT (limit > block);
2607
2608 for (match_base = block;
2609 match_base < limit;
2610 match_base += shift[*(match_base + pattern_size)])
2611 {
2612 match_ptr = match_base;
2613 match_size = 0;
2614
2615 // Compare pattern until it all matches, or we find a difference
2616 while (*match_ptr++ == pattern[match_size++])
2617 {
2618 ASSERT (match_size <= pattern_size &&
2619 match_ptr == (match_base + match_size));
2620
2621 // If we found a match, return the start address
2622 if (match_size >= pattern_size)
2623 return ((void*)(match_base));
2624
2625 }
2626 }
2627 return (NULL); // Found nothing
2628}
2629
2630/*
2631 *@@ strhtxtfind:
2632 * searches for a case-insensitive text pattern in a string
2633 * using the Boyer-Moore-Horspool-Sunday algorithm. The string and
2634 * pattern are null-terminated strings. Returns a pointer to the pattern
2635 * if found within the string, or NULL if the pattern was not found.
2636 * Will match strings irrespective of case. To match exact strings, use
2637 * strhfind(). Will not work on multibyte characters.
2638 *
2639 * Examples:
2640 + char *result;
2641 +
2642 + result = strhtxtfind ("AbracaDabra", "cad");
2643 + if (result)
2644 + puts (result);
2645 +
2646 * Taken from the "Standard Function Library", file sflfind.c.
2647 * Copyright: Copyright (c) 1991-99 iMatix Corporation.
2648 * Slightly modified.
2649 *
2650 *@@added V0.9.3 (2000-05-08) [umoeller]
2651 */
2652
2653char* strhtxtfind (const char *string, // String containing data
2654 const char *pattern) // Pattern to search for
2655{
2656 size_t
2657 shift [256]; // Shift distance for each value
2658 size_t
2659 string_size,
2660 pattern_size,
2661 byte_nbr, // Index into byte array
2662 match_size; // Size of matched part
2663 const char
2664 *match_base = NULL, // Base of match of pattern
2665 *match_ptr = NULL, // Point within current match
2666 *limit = NULL; // Last potiental match point
2667
2668 ASSERT (string); // Expect non-NULL pointers, but
2669 ASSERT (pattern); // fail gracefully if not debugging
2670 if (string == NULL || pattern == NULL)
2671 return (NULL);
2672
2673 string_size = strlen (string);
2674 pattern_size = strlen (pattern);
2675
2676 // Pattern must be smaller or equal in size to string
2677 if (string_size < pattern_size)
2678 return (NULL); // Otherwise it cannot be found
2679
2680 if (pattern_size == 0) // Empty string matches at start
2681 return (char *) string;
2682
2683 // Build the shift table
2684
2685 // The shift table determines how far to shift before trying to match
2686 // again, if a match at this point fails. If the byte after where the
2687 // end of our pattern falls is not in our pattern, then we start to
2688 // match again after that byte; otherwise we line up the last occurence
2689 // of that byte in our pattern under that byte, and try match again.
2690
2691 for (byte_nbr = 0; byte_nbr < 256; byte_nbr++)
2692 shift [byte_nbr] = pattern_size + 1;
2693
2694 for (byte_nbr = 0; byte_nbr < pattern_size; byte_nbr++)
2695 shift [(unsigned char) tolower (pattern [byte_nbr])] = pattern_size - byte_nbr;
2696
2697 // Search for the string. If we don't find a match, move up by the
2698 // amount we computed in the shift table above, to find location of
2699 // the next potiental match.
2700
2701 limit = string + (string_size - pattern_size + 1);
2702 ASSERT (limit > string);
2703
2704 for (match_base = string;
2705 match_base < limit;
2706 match_base += shift [(unsigned char) tolower (*(match_base + pattern_size))])
2707 {
2708 match_ptr = match_base;
2709 match_size = 0;
2710
2711 // Compare pattern until it all matches, or we find a difference
2712 while (tolower (*match_ptr++) == tolower (pattern [match_size++]))
2713 {
2714 ASSERT (match_size <= pattern_size &&
2715 match_ptr == (match_base + match_size));
2716
2717 // If we found a match, return the start address
2718 if (match_size >= pattern_size)
2719 return ((char *)(match_base));
2720 }
2721 }
2722 return (NULL); // Found nothing
2723}
2724
Note: See TracBrowser for help on using the repository browser.