Subversion Repositories Kolibri OS

Rev

Rev 9896 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. (*
  2.     Copyright 2020, 2022, 2023 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 <http://www.gnu.org/licenses/>.
  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, Encoding;
  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 textlen (body: XML.ELEMENT; VAR length: INTEGER);
  122. VAR
  123.     cur: XML.ELEMENT;
  124.  
  125. BEGIN
  126.     cur := body;
  127.     WHILE (cur # NIL) DO
  128.         IF cur IS XML.TAG THEN
  129.             textlen(cur(XML.TAG).child.first, length)
  130.         ELSIF cur IS XML.WORD THEN
  131.             INC(length, cur(XML.WORD).value.last - cur(XML.WORD).value.first + 1)
  132.         ELSIF cur IS XML.SPACE THEN
  133.             INC(length)
  134.         END;
  135.         cur := cur.next
  136.     END
  137. END textlen;
  138.  
  139.  
  140. PROCEDURE puttext (body: XML.ELEMENT; VAR buf: INTEGER);
  141. VAR
  142.     cur: XML.ELEMENT;
  143.     len: INTEGER;
  144.  
  145. BEGIN
  146.     cur := body;
  147.     WHILE (cur # NIL) DO
  148.         IF cur IS XML.TAG THEN
  149.             puttext(cur(XML.TAG).child.first, buf)
  150.         ELSIF cur IS XML.WORD THEN
  151.             len := cur(XML.WORD).value.last - cur(XML.WORD).value.first + 1;
  152.             SYSTEM.MOVE(cur(XML.WORD).value.first, buf, len);
  153.             INC(buf, len)
  154.         ELSIF cur IS XML.SPACE THEN
  155.             SYSTEM.PUT(buf, 20X);
  156.             INC(buf)
  157.         END;
  158.         cur := cur.next
  159.     END
  160. END puttext;
  161.  
  162.  
  163. PROCEDURE cap (code: INTEGER): INTEGER;
  164. BEGIN
  165.     CASE code OF
  166.     |61H..7AH, 430H..44FH:
  167.         DEC(code, 32)
  168.     |451H..45FH:
  169.         DEC(code, 80)
  170.     |491H:
  171.         code := 490H
  172.     ELSE
  173.     END
  174.     RETURN code
  175. END cap;
  176.  
  177.  
  178. PROCEDURE upcase (src, dst, length: INTEGER);
  179. VAR
  180.     n: INTEGER;
  181.     u: Encoding.tUtf8;
  182. BEGIN
  183.     WHILE length > 0 DO
  184.         Encoding.utf8(cap(Encoding.getUtf8Char(src, length)), u);
  185.         n := LENGTH(u);
  186.         SYSTEM.MOVE(SYSTEM.ADR(u[0]), dst, n);
  187.         INC(dst, n)
  188.     END
  189. END upcase;
  190.  
  191.  
  192. PROCEDURE create (body: XML.ELEMENT);
  193. VAR
  194.     length, buf, buf1, temp: INTEGER;
  195.     text: Text;
  196.     xml: XML.ELEMENT;
  197.  
  198.  
  199.     PROCEDURE index (idx: TextIdx; buf, length: INTEGER);
  200.     VAR
  201.         i: INTEGER;
  202.         c: CHAR;
  203.         offs, temp: INTEGER;
  204.  
  205.     BEGIN
  206.         idx.data := buf;
  207.         idx.size := length;
  208.  
  209.         FOR i := 0 TO 255 DO
  210.             idx.offs[i] := 0;
  211.             idx.cnt[i] := 0
  212.         END;
  213.  
  214.         i := length;
  215.  
  216.         WHILE i > 0 DO
  217.             SYSTEM.GET(buf, c);
  218.             INC(idx.offs[ORD(c)]);
  219.             DEC(i);
  220.             INC(buf)
  221.         END;
  222.  
  223.         offs := 0;
  224.  
  225.         FOR i := 0 TO 255 DO
  226.             temp := offs;
  227.             INC(offs, idx.offs[i]);
  228.             idx.offs[i] := temp * 4
  229.         END;
  230.  
  231.         idx.table := K.malloc(offs * 4);
  232.         SU.MemError(idx.table = 0);
  233.  
  234.         i := length;
  235.         buf := idx.data;
  236.  
  237.         WHILE i > 0 DO
  238.             SYSTEM.GET(buf, c);
  239.             SYSTEM.PUT(idx.table + idx.offs[ORD(c)] + idx.cnt[ORD(c)] * 4, length - i);
  240.             INC(idx.cnt[ORD(c)]);
  241.             DEC(i);
  242.             INC(buf)
  243.         END
  244.     END index;
  245.  
  246.  
  247. BEGIN
  248.     NEW(text);
  249.     text.body := body(XML.TAG);
  250.     text.PosList := LISTS.create(NIL);
  251.  
  252.     xml := body;
  253.     body := body(XML.TAG).child.first;
  254.     textlen(body, length);
  255.     buf := K.malloc(length);
  256.     SU.MemError(buf = 0);
  257.     temp := buf;
  258.     puttext(body, temp);
  259.  
  260.     NEW(text.idx0);
  261.     index(text.idx0, buf, length);
  262.  
  263.     buf1 := K.malloc(length);
  264.     SU.MemError(buf1 = 0);
  265.  
  266.     upcase(buf, buf1, length);
  267.  
  268.     NEW(text.idx1);
  269.     index(text.idx1, buf1, text.idx0.size);
  270.  
  271.     text.case := FALSE;
  272.  
  273.     text.str0 := "";
  274.     text.str1 := "";
  275.     xml(XML.TAG).text := text;
  276.     LISTS.push(TextList, text)
  277. END create;
  278.  
  279.  
  280. PROCEDURE select (body: XML.ELEMENT; VAR pos: TPos; VAR curpos, strong, italic, code: INTEGER);
  281. VAR
  282.     cur   : XML.ELEMENT;
  283.     word  : XML.WORD;
  284.     space : XML.SPACE;
  285.  
  286.     tag_value, len, wbeg, wend, selbeg, selend,
  287.     a, b, z, x, w: INTEGER;
  288.  
  289.  
  290.     PROCEDURE New (RectList: LISTS.LIST; x1, y1, x2, y2: INTEGER);
  291.     VAR rect: TRect;
  292.     BEGIN
  293.         NEW(rect);
  294.         rect.x1 := x1; rect.y1 := y1;
  295.         rect.x2 := x2; rect.y2 := y2;
  296.         LISTS.push(RectList, rect)
  297.     END New;
  298.  
  299.  
  300. BEGIN
  301.     cur := body;
  302.     WHILE (cur # NIL) & (pos # NIL) DO
  303.         selbeg := pos.first;
  304.         selend := pos.last;
  305.         IF cur IS XML.TAG THEN
  306.             tag_value := cur(XML.TAG).value;
  307.  
  308.             CASE tag_value OF
  309.             |XML.tag_title, XML.tag_strong, XML.tag_th:
  310.                 INC(strong);
  311.                 Font.Bold(TRUE)
  312.             |XML.tag_epigraph, XML.tag_cite, XML.tag_emphasis:
  313.                 INC(italic);
  314.                 Font.Italic(TRUE, FALSE)
  315.             |XML.tag_code:
  316.                 Font.sysfont(TRUE);
  317.                 INC(code)
  318.             ELSE
  319.             END;
  320.  
  321.             select(cur(XML.TAG).child.first, pos, curpos, strong, italic, code);
  322.  
  323.             CASE tag_value OF
  324.             |XML.tag_title, XML.tag_strong, XML.tag_th, XML.tag_text_author, XML.tag_date:
  325.                 DEC(strong);
  326.                 Font.Bold(strong > 0)
  327.             |XML.tag_epigraph, XML.tag_cite, XML.tag_emphasis:
  328.                 DEC(italic);
  329.                 Font.Italic(italic > 0, FALSE)
  330.             |XML.tag_code:
  331.                 DEC(code);
  332.                 Font.sysfont(code > 0)
  333.             ELSE
  334.             END;
  335.  
  336.             IF pos # NIL THEN
  337.                 selbeg := pos.first;
  338.                 selend := pos.last
  339.             END
  340.         ELSIF cur IS XML.WORD THEN
  341.             word := cur(XML.WORD);
  342.             len := word.value.last - word.value.first + 1;
  343.             wbeg := curpos;
  344.             wend := curpos + len - 1;
  345.             INC(curpos, len);
  346.  
  347.             a := MAX(wbeg, selbeg);
  348.             b := MIN(wend, selend);
  349.  
  350.             IF b >= a THEN
  351.                 x := word.width;
  352.                 IF (a = wbeg) & (b = wend) THEN
  353.                     New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
  354.                 ELSIF (a = selbeg) & (b = wend) THEN
  355.                     z := selbeg - wbeg;
  356.                     INC(word.value.first, z);
  357.                     word.width := Font.TextWidth(word.value, S.Utf8Length(word.value));
  358.                     INC(word.X, x - word.width);
  359.                     New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
  360.                     DEC(word.value.first, z);
  361.                     DEC(word.X, x - word.width)
  362.                 ELSIF (a = wbeg) & (b = selend) THEN
  363.                     z := wend - selend;
  364.                     DEC(word.value.last, z);
  365.                     word.width := Font.TextWidth(word.value, S.Utf8Length(word.value));
  366.                     New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
  367.                     INC(word.value.last, z)
  368.                 ELSIF (a = selbeg) & (b = selend) THEN
  369.                     z := selbeg - wbeg;
  370.                     w := wend - selend;
  371.                     INC(word.value.first, z);
  372.                     INC(word.X, x - Font.TextWidth(word.value, S.Utf8Length(word.value)));
  373.                     DEC(word.value.last, w);
  374.                     word.width := Font.TextWidth(word.value, S.Utf8Length(word.value));
  375.                     New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
  376.                     INC(word.value.last, w);
  377.                     DEC(word.X, x - Font.TextWidth(word.value, S.Utf8Length(word.value)));
  378.                     DEC(word.value.first, z)
  379.                 END;
  380.                 word.width := x
  381.             END
  382.         ELSIF cur IS XML.SPACE THEN
  383.             IF (selbeg <= curpos) & (curpos <= selend) THEN
  384.                 space := cur(XML.SPACE);
  385.                 New(pos.RectList, space.X, space.Y, space.X + space.width, space.Y + Font.FontH())
  386.             END;
  387.             len := 1;
  388.             INC(curpos)
  389.         END;
  390.         IF curpos > selend THEN
  391.             IF pos # NIL THEN
  392.                 pos := pos.next(TPos);
  393.             END;
  394.             IF cur IS XML.TEXT THEN
  395.                 DEC(curpos, len)
  396.             ELSE  (* tag *)
  397.                 cur := cur.next
  398.             END
  399.         ELSE
  400.             cur := cur.next
  401.         END
  402.     END
  403. END select;
  404.  
  405.  
  406. PROCEDURE streq (s1, s2, n: INTEGER): BOOLEAN;
  407. VAR
  408.     c1, c2: CHAR;
  409.  
  410. BEGIN
  411.     REPEAT
  412.         SYSTEM.GET(s1, c1); INC(s1);
  413.         SYSTEM.GET(s2, c2); INC(s2);
  414.         DEC(n)
  415.     UNTIL (n = 0) OR (c1 # c2)
  416.  
  417.     RETURN c1 = c2
  418. END streq;
  419.  
  420.  
  421. PROCEDURE destroy (VAR item: LISTS.ITEM);
  422. BEGIN
  423.     LISTS.destroy(item(TPos).RectList);
  424.     DISPOSE(item)
  425. END destroy;
  426.  
  427.  
  428. PROCEDURE find (body: XML.TAG; str: STRING);
  429. VAR
  430.     c: CHAR;
  431.     offs, i, pos, strong, italic, code: INTEGER;
  432.     posItem: TPos;
  433.     text: Text;
  434.     pstr, slen: INTEGER;
  435.     idx: TextIdx;
  436.  
  437. BEGIN
  438.     text := body.text(Text);
  439.     text.found := 0;
  440.     LISTS.destroy(text.PosList);
  441.     text.PosList := LISTS.create(NIL);
  442.  
  443.     text.str0 := str;
  444.     upcase(SYSTEM.ADR(str[0]), SYSTEM.ADR(text.str1[0]), LENGTH(str));
  445.  
  446.     IF text.case THEN
  447.         idx := text.idx0;
  448.         pstr := SYSTEM.ADR(text.str0[0])
  449.     ELSE
  450.         idx := text.idx1;
  451.         pstr := SYSTEM.ADR(text.str1[0])
  452.     END;
  453.  
  454.     slen := LENGTH(str);
  455.  
  456.     SYSTEM.GET(pstr, c);
  457.     offs := idx.offs[ORD(c)];
  458.     i := idx.cnt[ORD(c)];
  459.     WHILE i > 0 DO
  460.         SYSTEM.GET(idx.table + offs, pos);
  461.         INC(offs, 4);
  462.         IF (pos + slen <= idx.size) & streq(pstr, idx.data + pos, slen) THEN
  463.             NEW(posItem);
  464.             posItem.n := text.found;
  465.             posItem.first := pos;
  466.             posItem.last := pos + slen - 1;
  467.             posItem.RectList := LISTS.create(NIL);
  468.             posItem.destroy := destroy;
  469.             LISTS.push(text.PosList, posItem);
  470.             INC(text.found)
  471.         END;
  472.         DEC(i)
  473.     END;
  474.     posItem := text.PosList.first(TPos);
  475.     pos := 0; strong := 0; italic := 0; code := 0;
  476.     select(body.child.first, posItem, pos, strong, italic, code);
  477.     text.curPos := NIL
  478. END find;
  479.  
  480.  
  481. PROCEDURE ffirst (body: XML.TAG);
  482. VAR
  483.     text: Text;
  484.  
  485. BEGIN
  486.     text := body.text(Text);
  487.     IF text.str0 # "" THEN
  488.         find(body, text.str0);
  489.         text.curPos := text.PosList.first(TPos)
  490.     END
  491. END ffirst;
  492.  
  493.  
  494. PROCEDURE found* (body: XML.TAG): BOOLEAN;
  495.     RETURN (body # NIL) & (body.text # NIL) & (body.text(Text).found # 0)
  496. END found;
  497.  
  498.  
  499. PROCEDURE fnext* (body: XML.TAG; VAR y: INTEGER; d: INTEGER);
  500. VAR
  501.     text: Text;
  502.     rect: TRect;
  503.     cur:  LISTS.ITEM;
  504.  
  505. BEGIN
  506.     text := body.text(Text);
  507.     IF (text # NIL) & (text.found # 0) THEN
  508.         cur := text.curPos;
  509.         CASE d OF
  510.         |1:
  511.             IF cur.next # NIL THEN
  512.                 cur := cur.next
  513.             ELSE
  514.                 cur := text.PosList.first
  515.             END
  516.  
  517.         |-1:
  518.             IF cur.prev # NIL THEN
  519.                 cur := cur.prev
  520.             ELSE
  521.                 cur := text.PosList.last
  522.             END
  523.  
  524.         |0:
  525.             cur := text.PosList.first
  526.  
  527.         END;
  528.         text.curPos := cur(TPos);
  529.         rect := text.curPos.RectList.first(TRect);
  530.         IF rect # NIL THEN
  531.             y := rect.y1
  532.         END
  533.     ELSE
  534.         y := -1
  535.     END
  536. END fnext;
  537.  
  538.  
  539. PROCEDURE open* (_find: PFind);
  540. BEGIN
  541.     Find := _find;
  542.     SearchForm.open
  543. END open;
  544.  
  545.  
  546. PROCEDURE close*;
  547. VAR
  548.     text: Text;
  549.     body: XML.TAG;
  550.     nullptr: INTEGER;
  551.  
  552. BEGIN
  553.     body := Body;
  554.     text := body.text(Text);
  555.     IF text # NIL THEN
  556.         LISTS.destroy(text.PosList);
  557.         text.PosList := LISTS.create(NIL);
  558.         text.found := 0;
  559.         text.curPos := NIL;
  560.         nullptr := K.free(text.idx0.table);
  561.         nullptr := K.free(text.idx0.data);
  562.         nullptr := K.free(text.idx1.table);
  563.         nullptr := K.free(text.idx1.data);
  564.         DISPOSE(text.idx0);
  565.         DISPOSE(text.idx1);
  566.         DISPOSE(text);
  567.         body.text := NIL
  568.     END
  569. END close;
  570.  
  571.  
  572. PROCEDURE resize*;
  573. VAR
  574.     n: INTEGER;
  575.     text: Text;
  576.     item: LISTS.ITEM;
  577.  
  578. BEGIN
  579.     text := TextList.first(Text);
  580.     WHILE text # NIL DO
  581.         IF text.found # 0 THEN
  582.             n := text.curPos.n;
  583.             find(text.body, text.str0);
  584.             item := LISTS.get(text.PosList, n);
  585.             text.curPos := item(TPos)
  586.         END;
  587.         text := text.next(Text)
  588.     END
  589. END resize;
  590.  
  591.  
  592. PROCEDURE callback (case: BOOLEAN; str: STRING): BOOLEAN;
  593. VAR
  594.     body: XML.TAG;
  595.  
  596. BEGIN
  597.     body := Body;
  598.     IF body.text = NIL THEN
  599.         create(body)
  600.     END;
  601.     body.text(Text).case := case;
  602.     body.text(Text).str0 := str;
  603.     ffirst(body);
  604.     Find(0)
  605.  
  606.     RETURN body.text(Text).found # 0
  607. END callback;
  608.  
  609.  
  610. BEGIN
  611.     TextList := LISTS.create(NIL);
  612.     SearchForm.init(callback)
  613. END Search.