Subversion Repositories Kolibri OS

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
9896 akron1 1
(*
2
    Copyright 2020, 2022 Anton Krotov
3
 
4
    This file is part of fb2read.
5
 
6
    fb2read is free software: you can redistribute it and/or modify
7
    it under the terms of the GNU General Public License as published by
8
    the Free Software Foundation, either version 3 of the License, or
9
    (at your option) any later version.
10
 
11
    fb2read is distributed in the hope that it will be useful,
12
    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
    GNU General Public License for more details.
15
 
16
    You should have received a copy of the GNU General Public License
17
    along with fb2read. If not, see .
18
*)
19
 
20
MODULE Search;
21
 
22
IMPORT
23
 
24
    XML, G := Graph, Window, Font, S := Strings, LISTS, SYSTEM,
25
    SU := SysUtils, K := KOSAPI, SearchForm;
26
 
27
 
28
TYPE
29
 
30
    STRING* = SearchForm.STRING;
31
 
32
    PFind = PROCEDURE (d: INTEGER);
33
 
34
    TRect = POINTER TO RECORD (LISTS.ITEM)
35
 
36
        x1, y1, x2, y2: INTEGER
37
 
38
    END;
39
 
40
    TPos = POINTER TO RECORD (LISTS.ITEM)
41
 
42
        n, first, last: INTEGER;
43
        RectList: LISTS.LIST
44
 
45
    END;
46
 
47
    TextIdx = POINTER TO RECORD
48
 
49
        cnt, offs: ARRAY 256 OF INTEGER;
50
        table: INTEGER;
51
        data, size: INTEGER
52
 
53
    END;
54
 
55
    Text = POINTER TO RECORD (LISTS.ITEM)
56
 
57
        case: BOOLEAN;
58
        idx0, idx1: TextIdx;
59
        str0, str1: STRING;
60
        PosList: LISTS.LIST;
61
        curPos: TPos;
62
        found: INTEGER;
63
        body: XML.TAG
64
 
65
    END;
66
 
67
 
68
VAR
69
 
70
    TextList: LISTS.LIST;
71
    Body: XML.TAG;
72
    Find: PFind;
73
 
74
 
75
PROCEDURE SelText (Col: Window.TRect; min, max, Ycur, LineH: INTEGER; right: BOOLEAN; rect: TRect; cur: BOOLEAN);
76
VAR
77
    y, y0, color: INTEGER;
78
 
79
BEGIN
80
    y  := rect.y1 - Ycur;
81
    y0 := y - y MOD LineH;
82
    IF (min <= y0) & (y0 <= max) THEN
83
        IF cur THEN
84
            color := 0FF0000H
85
        ELSE
86
            color := 0
87
        END;
88
        G.BoxNotXOR(Col.Left + rect.x1 + 1, Col.Top + y - Col.Height * ORD(right), Col.Left + rect.x2, Col.Top + y - Col.Height * ORD(right) + Font.FontH(), color)
89
    END
90
END SelText;
91
 
92
 
93
PROCEDURE draw* (body: XML.TAG; ColLeft, ColRight: Window.TRect; Ycur, LineH: INTEGER; TwoCol: BOOLEAN);
94
VAR
95
    rect: TRect;
96
    pos, cur: TPos;
97
 
98
BEGIN
99
    Body := body;
100
    IF body.text # NIL THEN
101
        pos := body.text(Text).PosList.first(TPos);
102
        cur := body.text(Text).curPos
103
    ELSE
104
        pos := NIL;
105
        cur := NIL
106
    END;
107
    WHILE pos # NIL DO
108
        rect := pos.RectList.first(TRect);
109
        WHILE rect # NIL DO
110
            SelText(ColLeft, 0, ColLeft.Height - LineH, Ycur, LineH, FALSE, rect, pos = cur);
111
            IF TwoCol THEN
112
                SelText(ColRight, ColLeft.Height, ColLeft.Height + ColRight.Height - LineH, Ycur, LineH, TRUE, rect, pos = cur)
113
            END;
114
            rect := rect.next(TRect)
115
        END;
116
        pos := pos.next(TPos)
117
    END
118
END draw;
119
 
120
 
121
PROCEDURE getc_utf8 (VAR text, size, code: INTEGER);
122
VAR
123
    c:    BYTE;
124
    n, k: INTEGER;
125
    end:  BOOLEAN;
126
 
127
BEGIN
128
    ASSERT(size > 0);
129
    code := 0;
130
    end := FALSE;
131
    REPEAT
132
        SYSTEM.GET(text, c);
133
        INC(text);
134
        DEC(size);
135
        CASE c OF
136
        |  0..127:
137
            code := c;
138
            end := TRUE
139
 
140
        |128..191:
141
            code := code * 64 + c MOD 64;
142
            DEC(n);
143
            end := n <= 0
144
 
145
        |192..255:
146
            k := LSL(c, 24);
147
            n := -2;
148
            REPEAT
149
                k := ROR(k, -1);
150
                INC(n)
151
            UNTIL ~ODD(k);
152
            k := LSL(c, n + 25);
153
            code := LSR(k, n + 25)
154
 
155
        END
156
    UNTIL (size = 0) OR end
157
END getc_utf8;
158
 
159
 
160
PROCEDURE textlen (body: XML.ELEMENT; VAR length: INTEGER);
161
VAR
162
    cur: XML.ELEMENT;
163
 
164
BEGIN
165
    cur := body;
166
    WHILE (cur # NIL) DO
167
        IF cur IS XML.TAG THEN
168
            textlen(cur(XML.TAG).child.first, length)
169
        ELSIF cur IS XML.WORD THEN
170
            INC(length, cur(XML.WORD).value.last - cur(XML.WORD).value.first + 1)
171
        ELSIF cur IS XML.SPACE THEN
172
            INC(length)
173
        END;
174
        cur := cur.next
175
    END
176
END textlen;
177
 
178
 
179
PROCEDURE puttext (body: XML.ELEMENT; VAR buf: INTEGER);
180
VAR
181
    cur: XML.ELEMENT;
182
    len: INTEGER;
183
 
184
BEGIN
185
    cur := body;
186
    WHILE (cur # NIL) DO
187
        IF cur IS XML.TAG THEN
188
            puttext(cur(XML.TAG).child.first, buf)
189
        ELSIF cur IS XML.WORD THEN
190
            len := cur(XML.WORD).value.last - cur(XML.WORD).value.first + 1;
191
            SYSTEM.MOVE(cur(XML.WORD).value.first, buf, len);
192
            INC(buf, len)
193
        ELSIF cur IS XML.SPACE THEN
194
            SYSTEM.PUT(buf, 20X);
195
            INC(buf)
196
        END;
197
        cur := cur.next
198
    END
199
END puttext;
200
 
201
 
202
PROCEDURE cap (code: INTEGER): INTEGER;
203
BEGIN
204
    CASE code OF
205
    |61H..7AH, 430H..44FH:
206
        DEC(code, 32)
207
    |451H..45FH:
208
        DEC(code, 80)
209
    |491H:
210
        code := 490H
211
    ELSE
212
    END
213
    RETURN code
214
END cap;
215
 
216
 
217
PROCEDURE UpCase (s1, s2, length: INTEGER);
218
VAR
219
    code, n: INTEGER;
220
    u: S.UTF8;
221
 
222
BEGIN
223
    WHILE length > 0 DO
224
        getc_utf8(s1, length, code);
225
        S.utf8(cap(code), u);
226
        n := LENGTH(u);
227
        SYSTEM.MOVE(SYSTEM.ADR(u[0]), s2, n);
228
        INC(s2, n)
229
    END
230
END UpCase;
231
 
232
 
233
PROCEDURE create (body: XML.ELEMENT);
234
VAR
235
    length, buf, buf1, temp: INTEGER;
236
    text: Text;
237
    xml: XML.ELEMENT;
238
 
239
 
240
    PROCEDURE index (idx: TextIdx; buf, length: INTEGER);
241
    VAR
242
        i: INTEGER;
243
        c: CHAR;
244
        offs, temp: INTEGER;
245
 
246
    BEGIN
247
        idx.data := buf;
248
        idx.size := length;
249
 
250
        FOR i := 0 TO 255 DO
251
            idx.offs[i] := 0;
252
            idx.cnt[i] := 0
253
        END;
254
 
255
        i := length;
256
 
257
        WHILE i > 0 DO
258
            SYSTEM.GET(buf, c);
259
            INC(idx.offs[ORD(c)]);
260
            DEC(i);
261
            INC(buf)
262
        END;
263
 
264
        offs := 0;
265
 
266
        FOR i := 0 TO 255 DO
267
            temp := offs;
268
            INC(offs, idx.offs[i]);
269
            idx.offs[i] := temp * 4
270
        END;
271
 
272
        idx.table := K.malloc(offs * 4);
273
        SU.MemError(idx.table = 0);
274
 
275
        i := length;
276
        buf := idx.data;
277
 
278
        WHILE i > 0 DO
279
            SYSTEM.GET(buf, c);
280
            SYSTEM.PUT(idx.table + idx.offs[ORD(c)] + idx.cnt[ORD(c)] * 4, length - i);
281
            INC(idx.cnt[ORD(c)]);
282
            DEC(i);
283
            INC(buf)
284
        END
285
    END index;
286
 
287
 
288
BEGIN
289
    NEW(text);
290
    text.body := body(XML.TAG);
291
    text.PosList := LISTS.create(NIL);
292
 
293
    xml := body;
294
    body := body(XML.TAG).child.first;
295
    textlen(body, length);
296
    buf := K.malloc(length);
297
    SU.MemError(buf = 0);
298
    temp := buf;
299
    puttext(body, temp);
300
 
301
    NEW(text.idx0);
302
    index(text.idx0, buf, length);
303
 
304
    buf1 := K.malloc(length);
305
    SU.MemError(buf1 = 0);
306
 
307
    UpCase(buf, buf1, length);
308
 
309
    NEW(text.idx1);
310
    index(text.idx1, buf1, text.idx0.size);
311
 
312
    text.case := FALSE;
313
 
314
    text.str0 := "";
315
    text.str1 := "";
316
    xml(XML.TAG).text := text;
317
    LISTS.push(TextList, text)
318
END create;
319
 
320
 
321
PROCEDURE select (body: XML.ELEMENT; VAR pos: TPos; VAR curpos, strong, italic, code: INTEGER);
322
VAR
323
    cur   : XML.ELEMENT;
324
    word  : XML.WORD;
325
    space : XML.SPACE;
326
 
327
    tag_value, len, wbeg, wend, selbeg, selend,
328
    a, b, z, x, w: INTEGER;
329
 
330
 
331
    PROCEDURE New (RectList: LISTS.LIST; x1, y1, x2, y2: INTEGER);
332
    VAR rect: TRect;
333
    BEGIN
334
        NEW(rect);
335
        rect.x1 := x1; rect.y1 := y1;
336
        rect.x2 := x2; rect.y2 := y2;
337
        LISTS.push(RectList, rect)
338
    END New;
339
 
340
 
341
BEGIN
342
    cur := body;
343
    WHILE (cur # NIL) & (pos # NIL) DO
344
        selbeg := pos.first;
345
        selend := pos.last;
346
        IF cur IS XML.TAG THEN
347
            tag_value := cur(XML.TAG).value;
348
 
349
            CASE tag_value OF
350
            |XML.tag_title, XML.tag_strong, XML.tag_th:
351
                INC(strong);
352
                Font.Bold(TRUE)
353
            |XML.tag_epigraph, XML.tag_cite, XML.tag_emphasis:
354
                INC(italic);
355
                Font.Italic(TRUE, FALSE)
356
            |XML.tag_code:
357
                Font.sysfont(TRUE);
358
                INC(code)
359
            ELSE
360
            END;
361
 
362
            select(cur(XML.TAG).child.first, pos, curpos, strong, italic, code);
363
 
364
            CASE tag_value OF
365
            |XML.tag_title, XML.tag_strong, XML.tag_th, XML.tag_text_author, XML.tag_date:
366
                DEC(strong);
367
                Font.Bold(strong > 0)
368
            |XML.tag_epigraph, XML.tag_cite, XML.tag_emphasis:
369
                DEC(italic);
370
                Font.Italic(italic > 0, FALSE)
371
            |XML.tag_code:
372
                DEC(code);
373
                Font.sysfont(code > 0)
374
            ELSE
375
            END;
376
 
377
            IF pos # NIL THEN
378
                selbeg := pos.first;
379
                selend := pos.last
380
            END
381
        ELSIF cur IS XML.WORD THEN
382
            word := cur(XML.WORD);
383
            len := word.value.last - word.value.first + 1;
384
            wbeg := curpos;
385
            wend := curpos + len - 1;
386
            INC(curpos, len);
387
 
388
            a := MAX(wbeg, selbeg);
389
            b := MIN(wend, selend);
390
 
391
            IF b >= a THEN
392
                x := word.width;
393
                IF (a = wbeg) & (b = wend) THEN
394
                    New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
395
                ELSIF (a = selbeg) & (b = wend) THEN
396
                    z := selbeg - wbeg;
397
                    INC(word.value.first, z);
398
                    word.width := Font.TextWidth(word.value, S.Utf8Length(word.value));
399
                    INC(word.X, x - word.width);
400
                    New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
401
                    DEC(word.value.first, z);
402
                    DEC(word.X, x - word.width)
403
                ELSIF (a = wbeg) & (b = selend) THEN
404
                    z := wend - selend;
405
                    DEC(word.value.last, z);
406
                    word.width := Font.TextWidth(word.value, S.Utf8Length(word.value));
407
                    New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
408
                    INC(word.value.last, z)
409
                ELSIF (a = selbeg) & (b = selend) THEN
410
                    z := selbeg - wbeg;
411
                    w := wend - selend;
412
                    INC(word.value.first, z);
413
                    INC(word.X, x - Font.TextWidth(word.value, S.Utf8Length(word.value)));
414
                    DEC(word.value.last, w);
415
                    word.width := Font.TextWidth(word.value, S.Utf8Length(word.value));
416
                    New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
417
                    INC(word.value.last, w);
418
                    DEC(word.X, x - Font.TextWidth(word.value, S.Utf8Length(word.value)));
419
                    DEC(word.value.first, z)
420
                END;
421
                word.width := x
422
            END
423
        ELSIF cur IS XML.SPACE THEN
424
            IF (selbeg <= curpos) & (curpos <= selend) THEN
425
                space := cur(XML.SPACE);
426
                New(pos.RectList, space.X, space.Y, space.X + space.width, space.Y + Font.FontH())
427
            END;
428
            len := 1;
429
            INC(curpos)
430
        END;
431
        IF curpos > selend THEN
432
            IF pos # NIL THEN
433
                pos := pos.next(TPos);
434
            END;
435
            IF cur IS XML.TEXT THEN
436
                DEC(curpos, len)
437
            ELSE  (* tag *)
438
                cur := cur.next
439
            END
440
        ELSE
441
            cur := cur.next
442
        END
443
    END
444
END select;
445
 
446
 
447
PROCEDURE streq (s1, s2, n: INTEGER): BOOLEAN;
448
VAR
449
    c1, c2: CHAR;
450
 
451
BEGIN
452
    REPEAT
453
        SYSTEM.GET(s1, c1); INC(s1);
454
        SYSTEM.GET(s2, c2); INC(s2);
455
        DEC(n)
456
    UNTIL (n = 0) OR (c1 # c2)
457
 
458
    RETURN c1 = c2
459
END streq;
460
 
461
 
462
PROCEDURE destroy (VAR item: LISTS.ITEM);
463
BEGIN
464
    LISTS.destroy(item(TPos).RectList);
465
    DISPOSE(item)
466
END destroy;
467
 
468
 
469
PROCEDURE find (body: XML.TAG; str: STRING);
470
VAR
471
    c: CHAR;
472
    offs, i, pos, strong, italic, code: INTEGER;
473
    posItem: TPos;
474
    text: Text;
475
    pstr, slen: INTEGER;
476
    idx: TextIdx;
477
 
478
BEGIN
479
    text := body.text(Text);
480
    text.found := 0;
481
    LISTS.destroy(text.PosList);
482
    text.PosList := LISTS.create(NIL);
483
 
484
    text.str0 := str;
485
    UpCase(SYSTEM.ADR(str[0]), SYSTEM.ADR(text.str1[0]), LENGTH(str));
486
 
487
    IF text.case THEN
488
        idx := text.idx0;
489
        pstr := SYSTEM.ADR(text.str0[0])
490
    ELSE
491
        idx := text.idx1;
492
        pstr := SYSTEM.ADR(text.str1[0])
493
    END;
494
 
495
    slen := LENGTH(str);
496
 
497
    SYSTEM.GET(pstr, c);
498
    offs := idx.offs[ORD(c)];
499
    i := idx.cnt[ORD(c)];
500
    WHILE i > 0 DO
501
        SYSTEM.GET(idx.table + offs, pos);
502
        INC(offs, 4);
503
        IF (pos + slen <= idx.size) & streq(pstr, idx.data + pos, slen) THEN
504
            NEW(posItem);
505
            posItem.n := text.found;
506
            posItem.first := pos;
507
            posItem.last := pos + slen - 1;
508
            posItem.RectList := LISTS.create(NIL);
509
            posItem.destroy := destroy;
510
            LISTS.push(text.PosList, posItem);
511
            INC(text.found)
512
        END;
513
        DEC(i)
514
    END;
515
    posItem := text.PosList.first(TPos);
516
    pos := 0; strong := 0; italic := 0; code := 0;
517
    select(body.child.first, posItem, pos, strong, italic, code);
518
    text.curPos := NIL
519
END find;
520
 
521
 
522
PROCEDURE ffirst (body: XML.TAG);
523
VAR
524
    text: Text;
525
 
526
BEGIN
527
    text := body.text(Text);
528
    IF text.str0 # "" THEN
529
        find(body, text.str0);
530
        text.curPos := text.PosList.first(TPos)
531
    END
532
END ffirst;
533
 
534
 
535
PROCEDURE found* (body: XML.TAG): BOOLEAN;
536
    RETURN (body # NIL) & (body.text # NIL) & (body.text(Text).found # 0)
537
END found;
538
 
539
 
540
PROCEDURE fnext* (body: XML.TAG; VAR y: INTEGER; d: INTEGER);
541
VAR
542
    text: Text;
543
    rect: TRect;
544
    cur:  LISTS.ITEM;
545
 
546
BEGIN
547
    text := body.text(Text);
548
    IF (text # NIL) & (text.found # 0) THEN
549
        cur := text.curPos;
550
        CASE d OF
551
        |1:
552
            IF cur.next # NIL THEN
553
                cur := cur.next
554
            ELSE
555
                cur := text.PosList.first
556
            END
557
 
558
        |-1:
559
            IF cur.prev # NIL THEN
560
                cur := cur.prev
561
            ELSE
562
                cur := text.PosList.last
563
            END
564
 
565
        |0:
566
            cur := text.PosList.first
567
 
568
        END;
569
        text.curPos := cur(TPos);
570
        rect := text.curPos.RectList.first(TRect);
571
        IF rect # NIL THEN
572
            y := rect.y1
573
        END
574
    ELSE
575
        y := -1
576
    END
577
END fnext;
578
 
579
 
580
PROCEDURE open* (_find: PFind);
581
BEGIN
582
    Find := _find;
583
    SearchForm.open
584
END open;
585
 
586
 
587
PROCEDURE close*;
588
VAR
589
    text: Text;
590
    body: XML.TAG;
591
 
592
BEGIN
593
    body := Body;
594
    text := body.text(Text);
595
    IF text # NIL THEN
596
        LISTS.destroy(text.PosList);
597
        text.PosList := LISTS.create(NIL);
598
        text.found := 0;
599
        text.curPos := NIL
600
    END
601
END close;
602
 
603
 
604
PROCEDURE resize*;
605
VAR
606
    n: INTEGER;
607
    text: Text;
608
    item: LISTS.ITEM;
609
 
610
BEGIN
611
    text := TextList.first(Text);
612
    WHILE text # NIL DO
613
        IF text.found # 0 THEN
614
            n := text.curPos.n;
615
            find(text.body, text.str0);
616
            item := LISTS.get(text.PosList, n);
617
            text.curPos := item(TPos)
618
        END;
619
        text := text.next(Text)
620
    END
621
END resize;
622
 
623
 
624
PROCEDURE callback (case: BOOLEAN; str: STRING): BOOLEAN;
625
VAR
626
    body: XML.TAG;
627
 
628
BEGIN
629
    body := Body;
630
    IF body.text = NIL THEN
631
        create(body)
632
    END;
633
    body.text(Text).case := case;
634
    body.text(Text).str0 := str;
635
    ffirst(body);
636
    Find(0)
637
 
638
    RETURN body.text(Text).found # 0
639
END callback;
640
 
641
 
642
BEGIN
643
    TextList := LISTS.create(NIL);
644
    SearchForm.init(callback)
645
END Search.