Subversion Repositories Kolibri OS

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  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 <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;
  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.