forked from KolibriOS/kolibrios
c56f682434
git-svn-id: svn://kolibrios.org@9898 a494cfbc-eb01-0410-851d-a64ba20cac60
613 lines
15 KiB
Plaintext
613 lines
15 KiB
Plaintext
(*
|
|
Copyright 2020, 2022, 2023 Anton Krotov
|
|
|
|
This file is part of fb2read.
|
|
|
|
fb2read is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
fb2read is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with fb2read. If not, see <http://www.gnu.org/licenses/>.
|
|
*)
|
|
|
|
MODULE Search;
|
|
|
|
IMPORT
|
|
|
|
XML, G := Graph, Window, Font, S := Strings, LISTS, SYSTEM,
|
|
SU := SysUtils, K := KOSAPI, SearchForm, Encoding;
|
|
|
|
|
|
TYPE
|
|
|
|
STRING* = SearchForm.STRING;
|
|
|
|
PFind = PROCEDURE (d: INTEGER);
|
|
|
|
TRect = POINTER TO RECORD (LISTS.ITEM)
|
|
|
|
x1, y1, x2, y2: INTEGER
|
|
|
|
END;
|
|
|
|
TPos = POINTER TO RECORD (LISTS.ITEM)
|
|
|
|
n, first, last: INTEGER;
|
|
RectList: LISTS.LIST
|
|
|
|
END;
|
|
|
|
TextIdx = POINTER TO RECORD
|
|
|
|
cnt, offs: ARRAY 256 OF INTEGER;
|
|
table: INTEGER;
|
|
data, size: INTEGER
|
|
|
|
END;
|
|
|
|
Text = POINTER TO RECORD (LISTS.ITEM)
|
|
|
|
case: BOOLEAN;
|
|
idx0, idx1: TextIdx;
|
|
str0, str1: STRING;
|
|
PosList: LISTS.LIST;
|
|
curPos: TPos;
|
|
found: INTEGER;
|
|
body: XML.TAG
|
|
|
|
END;
|
|
|
|
|
|
VAR
|
|
|
|
TextList: LISTS.LIST;
|
|
Body: XML.TAG;
|
|
Find: PFind;
|
|
|
|
|
|
PROCEDURE SelText (Col: Window.tRect; min, max, Ycur, LineH: INTEGER; right: BOOLEAN; rect: TRect; cur: BOOLEAN);
|
|
VAR
|
|
y, y0, color: INTEGER;
|
|
|
|
BEGIN
|
|
y := rect.y1 - Ycur;
|
|
y0 := y - y MOD LineH;
|
|
IF (min <= y0) & (y0 <= max) THEN
|
|
IF cur THEN
|
|
color := 0FF0000H
|
|
ELSE
|
|
color := 0
|
|
END;
|
|
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)
|
|
END
|
|
END SelText;
|
|
|
|
|
|
PROCEDURE draw* (body: XML.TAG; ColLeft, ColRight: Window.tRect; Ycur, LineH: INTEGER; TwoCol: BOOLEAN);
|
|
VAR
|
|
rect: TRect;
|
|
pos, cur: TPos;
|
|
|
|
BEGIN
|
|
Body := body;
|
|
IF body.text # NIL THEN
|
|
pos := body.text(Text).PosList.first(TPos);
|
|
cur := body.text(Text).curPos
|
|
ELSE
|
|
pos := NIL;
|
|
cur := NIL
|
|
END;
|
|
WHILE pos # NIL DO
|
|
rect := pos.RectList.first(TRect);
|
|
WHILE rect # NIL DO
|
|
SelText(ColLeft, 0, ColLeft.height - LineH, Ycur, LineH, FALSE, rect, pos = cur);
|
|
IF TwoCol THEN
|
|
SelText(ColRight, ColLeft.height, ColLeft.height + ColRight.height - LineH, Ycur, LineH, TRUE, rect, pos = cur)
|
|
END;
|
|
rect := rect.next(TRect)
|
|
END;
|
|
pos := pos.next(TPos)
|
|
END
|
|
END draw;
|
|
|
|
|
|
PROCEDURE textlen (body: XML.ELEMENT; VAR length: INTEGER);
|
|
VAR
|
|
cur: XML.ELEMENT;
|
|
|
|
BEGIN
|
|
cur := body;
|
|
WHILE (cur # NIL) DO
|
|
IF cur IS XML.TAG THEN
|
|
textlen(cur(XML.TAG).child.first, length)
|
|
ELSIF cur IS XML.WORD THEN
|
|
INC(length, cur(XML.WORD).value.last - cur(XML.WORD).value.first + 1)
|
|
ELSIF cur IS XML.SPACE THEN
|
|
INC(length)
|
|
END;
|
|
cur := cur.next
|
|
END
|
|
END textlen;
|
|
|
|
|
|
PROCEDURE puttext (body: XML.ELEMENT; VAR buf: INTEGER);
|
|
VAR
|
|
cur: XML.ELEMENT;
|
|
len: INTEGER;
|
|
|
|
BEGIN
|
|
cur := body;
|
|
WHILE (cur # NIL) DO
|
|
IF cur IS XML.TAG THEN
|
|
puttext(cur(XML.TAG).child.first, buf)
|
|
ELSIF cur IS XML.WORD THEN
|
|
len := cur(XML.WORD).value.last - cur(XML.WORD).value.first + 1;
|
|
SYSTEM.MOVE(cur(XML.WORD).value.first, buf, len);
|
|
INC(buf, len)
|
|
ELSIF cur IS XML.SPACE THEN
|
|
SYSTEM.PUT(buf, 20X);
|
|
INC(buf)
|
|
END;
|
|
cur := cur.next
|
|
END
|
|
END puttext;
|
|
|
|
|
|
PROCEDURE cap (code: INTEGER): INTEGER;
|
|
BEGIN
|
|
CASE code OF
|
|
|61H..7AH, 430H..44FH:
|
|
DEC(code, 32)
|
|
|451H..45FH:
|
|
DEC(code, 80)
|
|
|491H:
|
|
code := 490H
|
|
ELSE
|
|
END
|
|
RETURN code
|
|
END cap;
|
|
|
|
|
|
PROCEDURE upcase (src, dst, length: INTEGER);
|
|
VAR
|
|
n: INTEGER;
|
|
u: Encoding.tUtf8;
|
|
BEGIN
|
|
WHILE length > 0 DO
|
|
Encoding.utf8(cap(Encoding.getUtf8Char(src, length)), u);
|
|
n := LENGTH(u);
|
|
SYSTEM.MOVE(SYSTEM.ADR(u[0]), dst, n);
|
|
INC(dst, n)
|
|
END
|
|
END upcase;
|
|
|
|
|
|
PROCEDURE create (body: XML.ELEMENT);
|
|
VAR
|
|
length, buf, buf1, temp: INTEGER;
|
|
text: Text;
|
|
xml: XML.ELEMENT;
|
|
|
|
|
|
PROCEDURE index (idx: TextIdx; buf, length: INTEGER);
|
|
VAR
|
|
i: INTEGER;
|
|
c: CHAR;
|
|
offs, temp: INTEGER;
|
|
|
|
BEGIN
|
|
idx.data := buf;
|
|
idx.size := length;
|
|
|
|
FOR i := 0 TO 255 DO
|
|
idx.offs[i] := 0;
|
|
idx.cnt[i] := 0
|
|
END;
|
|
|
|
i := length;
|
|
|
|
WHILE i > 0 DO
|
|
SYSTEM.GET(buf, c);
|
|
INC(idx.offs[ORD(c)]);
|
|
DEC(i);
|
|
INC(buf)
|
|
END;
|
|
|
|
offs := 0;
|
|
|
|
FOR i := 0 TO 255 DO
|
|
temp := offs;
|
|
INC(offs, idx.offs[i]);
|
|
idx.offs[i] := temp * 4
|
|
END;
|
|
|
|
idx.table := K.malloc(offs * 4);
|
|
SU.MemError(idx.table = 0);
|
|
|
|
i := length;
|
|
buf := idx.data;
|
|
|
|
WHILE i > 0 DO
|
|
SYSTEM.GET(buf, c);
|
|
SYSTEM.PUT(idx.table + idx.offs[ORD(c)] + idx.cnt[ORD(c)] * 4, length - i);
|
|
INC(idx.cnt[ORD(c)]);
|
|
DEC(i);
|
|
INC(buf)
|
|
END
|
|
END index;
|
|
|
|
|
|
BEGIN
|
|
NEW(text);
|
|
text.body := body(XML.TAG);
|
|
text.PosList := LISTS.create(NIL);
|
|
|
|
xml := body;
|
|
body := body(XML.TAG).child.first;
|
|
textlen(body, length);
|
|
buf := K.malloc(length);
|
|
SU.MemError(buf = 0);
|
|
temp := buf;
|
|
puttext(body, temp);
|
|
|
|
NEW(text.idx0);
|
|
index(text.idx0, buf, length);
|
|
|
|
buf1 := K.malloc(length);
|
|
SU.MemError(buf1 = 0);
|
|
|
|
upcase(buf, buf1, length);
|
|
|
|
NEW(text.idx1);
|
|
index(text.idx1, buf1, text.idx0.size);
|
|
|
|
text.case := FALSE;
|
|
|
|
text.str0 := "";
|
|
text.str1 := "";
|
|
xml(XML.TAG).text := text;
|
|
LISTS.push(TextList, text)
|
|
END create;
|
|
|
|
|
|
PROCEDURE select (body: XML.ELEMENT; VAR pos: TPos; VAR curpos, strong, italic, code: INTEGER);
|
|
VAR
|
|
cur : XML.ELEMENT;
|
|
word : XML.WORD;
|
|
space : XML.SPACE;
|
|
|
|
tag_value, len, wbeg, wend, selbeg, selend,
|
|
a, b, z, x, w: INTEGER;
|
|
|
|
|
|
PROCEDURE New (RectList: LISTS.LIST; x1, y1, x2, y2: INTEGER);
|
|
VAR rect: TRect;
|
|
BEGIN
|
|
NEW(rect);
|
|
rect.x1 := x1; rect.y1 := y1;
|
|
rect.x2 := x2; rect.y2 := y2;
|
|
LISTS.push(RectList, rect)
|
|
END New;
|
|
|
|
|
|
BEGIN
|
|
cur := body;
|
|
WHILE (cur # NIL) & (pos # NIL) DO
|
|
selbeg := pos.first;
|
|
selend := pos.last;
|
|
IF cur IS XML.TAG THEN
|
|
tag_value := cur(XML.TAG).value;
|
|
|
|
CASE tag_value OF
|
|
|XML.tag_title, XML.tag_strong, XML.tag_th:
|
|
INC(strong);
|
|
Font.Bold(TRUE)
|
|
|XML.tag_epigraph, XML.tag_cite, XML.tag_emphasis:
|
|
INC(italic);
|
|
Font.Italic(TRUE, FALSE)
|
|
|XML.tag_code:
|
|
Font.sysfont(TRUE);
|
|
INC(code)
|
|
ELSE
|
|
END;
|
|
|
|
select(cur(XML.TAG).child.first, pos, curpos, strong, italic, code);
|
|
|
|
CASE tag_value OF
|
|
|XML.tag_title, XML.tag_strong, XML.tag_th, XML.tag_text_author, XML.tag_date:
|
|
DEC(strong);
|
|
Font.Bold(strong > 0)
|
|
|XML.tag_epigraph, XML.tag_cite, XML.tag_emphasis:
|
|
DEC(italic);
|
|
Font.Italic(italic > 0, FALSE)
|
|
|XML.tag_code:
|
|
DEC(code);
|
|
Font.sysfont(code > 0)
|
|
ELSE
|
|
END;
|
|
|
|
IF pos # NIL THEN
|
|
selbeg := pos.first;
|
|
selend := pos.last
|
|
END
|
|
ELSIF cur IS XML.WORD THEN
|
|
word := cur(XML.WORD);
|
|
len := word.value.last - word.value.first + 1;
|
|
wbeg := curpos;
|
|
wend := curpos + len - 1;
|
|
INC(curpos, len);
|
|
|
|
a := MAX(wbeg, selbeg);
|
|
b := MIN(wend, selend);
|
|
|
|
IF b >= a THEN
|
|
x := word.width;
|
|
IF (a = wbeg) & (b = wend) THEN
|
|
New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
|
|
ELSIF (a = selbeg) & (b = wend) THEN
|
|
z := selbeg - wbeg;
|
|
INC(word.value.first, z);
|
|
word.width := Font.TextWidth(word.value, S.Utf8Length(word.value));
|
|
INC(word.X, x - word.width);
|
|
New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
|
|
DEC(word.value.first, z);
|
|
DEC(word.X, x - word.width)
|
|
ELSIF (a = wbeg) & (b = selend) THEN
|
|
z := wend - selend;
|
|
DEC(word.value.last, z);
|
|
word.width := Font.TextWidth(word.value, S.Utf8Length(word.value));
|
|
New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
|
|
INC(word.value.last, z)
|
|
ELSIF (a = selbeg) & (b = selend) THEN
|
|
z := selbeg - wbeg;
|
|
w := wend - selend;
|
|
INC(word.value.first, z);
|
|
INC(word.X, x - Font.TextWidth(word.value, S.Utf8Length(word.value)));
|
|
DEC(word.value.last, w);
|
|
word.width := Font.TextWidth(word.value, S.Utf8Length(word.value));
|
|
New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
|
|
INC(word.value.last, w);
|
|
DEC(word.X, x - Font.TextWidth(word.value, S.Utf8Length(word.value)));
|
|
DEC(word.value.first, z)
|
|
END;
|
|
word.width := x
|
|
END
|
|
ELSIF cur IS XML.SPACE THEN
|
|
IF (selbeg <= curpos) & (curpos <= selend) THEN
|
|
space := cur(XML.SPACE);
|
|
New(pos.RectList, space.X, space.Y, space.X + space.width, space.Y + Font.FontH())
|
|
END;
|
|
len := 1;
|
|
INC(curpos)
|
|
END;
|
|
IF curpos > selend THEN
|
|
IF pos # NIL THEN
|
|
pos := pos.next(TPos);
|
|
END;
|
|
IF cur IS XML.TEXT THEN
|
|
DEC(curpos, len)
|
|
ELSE (* tag *)
|
|
cur := cur.next
|
|
END
|
|
ELSE
|
|
cur := cur.next
|
|
END
|
|
END
|
|
END select;
|
|
|
|
|
|
PROCEDURE streq (s1, s2, n: INTEGER): BOOLEAN;
|
|
VAR
|
|
c1, c2: CHAR;
|
|
|
|
BEGIN
|
|
REPEAT
|
|
SYSTEM.GET(s1, c1); INC(s1);
|
|
SYSTEM.GET(s2, c2); INC(s2);
|
|
DEC(n)
|
|
UNTIL (n = 0) OR (c1 # c2)
|
|
|
|
RETURN c1 = c2
|
|
END streq;
|
|
|
|
|
|
PROCEDURE destroy (VAR item: LISTS.ITEM);
|
|
BEGIN
|
|
LISTS.destroy(item(TPos).RectList);
|
|
DISPOSE(item)
|
|
END destroy;
|
|
|
|
|
|
PROCEDURE find (body: XML.TAG; str: STRING);
|
|
VAR
|
|
c: CHAR;
|
|
offs, i, pos, strong, italic, code: INTEGER;
|
|
posItem: TPos;
|
|
text: Text;
|
|
pstr, slen: INTEGER;
|
|
idx: TextIdx;
|
|
|
|
BEGIN
|
|
text := body.text(Text);
|
|
text.found := 0;
|
|
LISTS.destroy(text.PosList);
|
|
text.PosList := LISTS.create(NIL);
|
|
|
|
text.str0 := str;
|
|
upcase(SYSTEM.ADR(str[0]), SYSTEM.ADR(text.str1[0]), LENGTH(str));
|
|
|
|
IF text.case THEN
|
|
idx := text.idx0;
|
|
pstr := SYSTEM.ADR(text.str0[0])
|
|
ELSE
|
|
idx := text.idx1;
|
|
pstr := SYSTEM.ADR(text.str1[0])
|
|
END;
|
|
|
|
slen := LENGTH(str);
|
|
|
|
SYSTEM.GET(pstr, c);
|
|
offs := idx.offs[ORD(c)];
|
|
i := idx.cnt[ORD(c)];
|
|
WHILE i > 0 DO
|
|
SYSTEM.GET(idx.table + offs, pos);
|
|
INC(offs, 4);
|
|
IF (pos + slen <= idx.size) & streq(pstr, idx.data + pos, slen) THEN
|
|
NEW(posItem);
|
|
posItem.n := text.found;
|
|
posItem.first := pos;
|
|
posItem.last := pos + slen - 1;
|
|
posItem.RectList := LISTS.create(NIL);
|
|
posItem.destroy := destroy;
|
|
LISTS.push(text.PosList, posItem);
|
|
INC(text.found)
|
|
END;
|
|
DEC(i)
|
|
END;
|
|
posItem := text.PosList.first(TPos);
|
|
pos := 0; strong := 0; italic := 0; code := 0;
|
|
select(body.child.first, posItem, pos, strong, italic, code);
|
|
text.curPos := NIL
|
|
END find;
|
|
|
|
|
|
PROCEDURE ffirst (body: XML.TAG);
|
|
VAR
|
|
text: Text;
|
|
|
|
BEGIN
|
|
text := body.text(Text);
|
|
IF text.str0 # "" THEN
|
|
find(body, text.str0);
|
|
text.curPos := text.PosList.first(TPos)
|
|
END
|
|
END ffirst;
|
|
|
|
|
|
PROCEDURE found* (body: XML.TAG): BOOLEAN;
|
|
RETURN (body # NIL) & (body.text # NIL) & (body.text(Text).found # 0)
|
|
END found;
|
|
|
|
|
|
PROCEDURE fnext* (body: XML.TAG; VAR y: INTEGER; d: INTEGER);
|
|
VAR
|
|
text: Text;
|
|
rect: TRect;
|
|
cur: LISTS.ITEM;
|
|
|
|
BEGIN
|
|
text := body.text(Text);
|
|
IF (text # NIL) & (text.found # 0) THEN
|
|
cur := text.curPos;
|
|
CASE d OF
|
|
|1:
|
|
IF cur.next # NIL THEN
|
|
cur := cur.next
|
|
ELSE
|
|
cur := text.PosList.first
|
|
END
|
|
|
|
|-1:
|
|
IF cur.prev # NIL THEN
|
|
cur := cur.prev
|
|
ELSE
|
|
cur := text.PosList.last
|
|
END
|
|
|
|
|0:
|
|
cur := text.PosList.first
|
|
|
|
END;
|
|
text.curPos := cur(TPos);
|
|
rect := text.curPos.RectList.first(TRect);
|
|
IF rect # NIL THEN
|
|
y := rect.y1
|
|
END
|
|
ELSE
|
|
y := -1
|
|
END
|
|
END fnext;
|
|
|
|
|
|
PROCEDURE open* (_find: PFind);
|
|
BEGIN
|
|
Find := _find;
|
|
SearchForm.open
|
|
END open;
|
|
|
|
|
|
PROCEDURE close*;
|
|
VAR
|
|
text: Text;
|
|
body: XML.TAG;
|
|
nullptr: INTEGER;
|
|
|
|
BEGIN
|
|
body := Body;
|
|
text := body.text(Text);
|
|
IF text # NIL THEN
|
|
LISTS.destroy(text.PosList);
|
|
text.PosList := LISTS.create(NIL);
|
|
text.found := 0;
|
|
text.curPos := NIL;
|
|
nullptr := K.free(text.idx0.table);
|
|
nullptr := K.free(text.idx0.data);
|
|
nullptr := K.free(text.idx1.table);
|
|
nullptr := K.free(text.idx1.data);
|
|
DISPOSE(text.idx0);
|
|
DISPOSE(text.idx1);
|
|
DISPOSE(text);
|
|
body.text := NIL
|
|
END
|
|
END close;
|
|
|
|
|
|
PROCEDURE resize*;
|
|
VAR
|
|
n: INTEGER;
|
|
text: Text;
|
|
item: LISTS.ITEM;
|
|
|
|
BEGIN
|
|
text := TextList.first(Text);
|
|
WHILE text # NIL DO
|
|
IF text.found # 0 THEN
|
|
n := text.curPos.n;
|
|
find(text.body, text.str0);
|
|
item := LISTS.get(text.PosList, n);
|
|
text.curPos := item(TPos)
|
|
END;
|
|
text := text.next(Text)
|
|
END
|
|
END resize;
|
|
|
|
|
|
PROCEDURE callback (case: BOOLEAN; str: STRING): BOOLEAN;
|
|
VAR
|
|
body: XML.TAG;
|
|
|
|
BEGIN
|
|
body := Body;
|
|
IF body.text = NIL THEN
|
|
create(body)
|
|
END;
|
|
body.text(Text).case := case;
|
|
body.text(Text).str0 := str;
|
|
ffirst(body);
|
|
Find(0)
|
|
|
|
RETURN body.text(Text).found # 0
|
|
END callback;
|
|
|
|
|
|
BEGIN
|
|
TextList := LISTS.create(NIL);
|
|
SearchForm.init(callback)
|
|
END Search. |