PageSourceSearch

https://yuren-tw.github.io/textRevealer/textMarker.js

js yuren-tw.github.io collected 2026-10-03 10:13:48 UTC 6,810 bytes, 245 lines download raw bytes

1class StringDimension {
2  constructor(logical=0, physical=0) {
3    this.logical = logical;  // [...string].length
4    this.physical = physical;  // string.length
5  }
6  static plus(x, y) {
7    return new StringDimension(
8      x.logical + y.logical,
9      x.physical + y.physical
10    );
11  }
12  static addChar(x, char) {
13    return new StringDimension(
14      x.logical + 1,
15      x.physical + char.length
16    );
17  }
18}
19
20function* charIterator(string, end=true) {
21  let index = new StringDimension();
22  for (let char of string) {
23    yield [index, char];
24    index = StringDimension.addChar(index, char);
25  }
26  if (end)
27    yield [index, ''];
28}
29
30
31class SymbolManager {
32  constructor() {
33    this.trie = new Map();
34    this.dict = new Map();
35  }
36  take(symbol) {
37    if (!this.dict.has(symbol)) {
38      this.trieAdd(symbol);
39      this.dict.set(symbol, {
40        opening: false,
41        closing: false,
42        alone: false,
43        view: undefined
44      })
45    }
46    return this.dict.get(symbol);
47  }
48  setView(symbol, view) {
49    this.take(symbol).view = view;
50  }
51  trieAdd(symbol) {
52    let trie = this.trie;
53    for (let char of symbol)
54      trie = trie.get(char) || trie.set(char, new Map()).get(char);
55    trie.set('END', symbol);
56  }
57}
58
59class MarkManager {
60  constructor() {
61    this.dict = new Map();
62    this.refresh();
63  }
64  refresh() {
65    this.list = [];
66    this.amount = new Map();
67  }
68  take(symbol, mark='-UNDEFINED') {
69    if (!this.dict.has(symbol))
70      this.dict.set(symbol, {
71        mark: mark,
72        closedBy: [],
73        consume: true
74      });
75    return this.dict.get(symbol);
76  }
77  getUnduplicatedList() {
78    return Array.from(this.amount.keys(), symbol => this.dict.get(symbol).mark);
79  }
80  open(symbol) {
81    let amount = this.amount.get(symbol) || 0;
82    this.amount.set(symbol, amount + 1);
83    this.list.push(symbol);
84  }
85  close(symbol) {
86    let notYetClosedMarks = [];
87    let closedMarks = [];
88    while (this.list.length) {
89      let opening = this.list.pop();
90      if (!this.take(opening).closedBy.includes(symbol)) {
91        notYetClosedMarks.unshift(opening);
92        continue;
93      }
94      this.amountDec(opening);
95      closedMarks.push(opening);
96      if (this.take(opening).consume)
97        break;
98    }
99    this.list = this.list.concat(notYetClosedMarks);
100    return closedMarks;
101  }
102  amountDec(symbol) {
103    let amount = this.amount.get(symbol) || 1;
104    this.amount.set(symbol, amount - 1);
105    if (amount === 1)
106      this.amount.delete(symbol);
107  }
108}
109
110class ContextManager {
111  constructor() {
112    this.Symbol = new SymbolManager();
113    this.Mark = new MarkManager();
114  }
115  setSymbolView(symbol, view) {
116    this.Symbol.setView(symbol, view);
117  }
118  addMarkOnly(mark, symbol) {
119    this.Symbol.take(symbol).alone = true;
120    this.Mark.take(symbol, mark);
121  }
122  addMarkBetween(mark, opening, closing=opening, consume=true) {
123    this.Symbol.take(opening).opening = true;
124    this.Symbol.take(closing).closing = true;
125    this.Mark.take(opening, mark).closedBy.push(closing);
126    if (!consume)
127      this.Mark.take(opening).consume = false;
128  }
129  addMarkAfter(mark, leading, closedBySpace=false) {
130    this.addMarkBetween(mark, leading, '\n', false);
131    if (closedBySpace)
132      this.addMarkBetween(mark, leading, ' ', false);
133  }
134}
135
136
137function mkTextObj(rawContent, start, symbolId) {
138  return {
139    isSymbol: symbolId !== undefined,
140    symbolId: symbolId,
141    raw: rawContent,
142    view: rawContent,
143    start: start
144  };
145}
146function newBranch(trie, start) {
147  return {
148    currNode: trie,
149    achieved: undefined,
150    start: start,
151    currText: '',
152    currTextLength: new StringDimension(),
153    inactive: false
154  };
155}
156function walkBranches(branchList, char) {
157  let nextBranchList = [];
158  for (let branch of branchList) {
159    if (!branch.currNode.has(char))
160      branch.inactive = true;
161    if (branch.inactive) {
162      if (branch.achieved !== undefined)
163        nextBranchList.push(branch);
164      continue;
165    }
166    nextBranchList.push(branch);
167    branch.currNode = branch.currNode.get(char);
168    branch.currText += char;
169    branch.currTextLength = StringDimension.addChar(branch.currTextLength, char);
170    if (branch.currNode.has('END')) {
171      branch.achieved = {
172        symbolId: branch.currNode.get('END'),
173        text: branch.currText,
174        textLength: branch.currTextLength
175      };
176      break;  // we don't need the branches in the rest of the list
177    }
178  }
179  return nextBranchList;
180}
181function* checkBranches(branchList, buffer) {
182  while (branchList.length && branchList[0].inactive) {
183    let branch = branchList.shift();
184    let prefixLength = branch.start.logical - buffer.start.logical;
185    if (prefixLength > 0) {
186      let text = buffer.chars.splice(0, prefixLength).join('');
187      yield mkTextObj(text, buffer.start);
188    }
189    buffer.chars.splice(0, branch.achieved.textLength.logical);
190    buffer.start = StringDimension.plus(branch.start, branch.achieved.textLength);
191    yield mkTextObj(branch.achieved.text, branch.start, branch.achieved.symbolId);
192  }
193  return [branchList, buffer];
194}
195function* textObjGenerator(symbolManager, wholeContent) {
196  let branchList = [];
197  let buffer = {
198    chars: [],
199    start: new StringDimension(),
200  };
201  for (let [index, char] of charIterator(wholeContent)) {
202    buffer.chars.push(char);
203    if (symbolManager.trie.has(char))
204      branchList.push(newBranch(symbolManager.trie, index));
205    branchList = walkBranches(branchList, char);
206    [branchList, buffer] = yield* checkBranches(branchList, buffer);
207  }
208  let remainder = buffer.chars.join('');
209  if (remainder.length > 0)
210    yield mkTextObj(remainder, buffer.start);
211}
212
213
214function mkTextMark(textObj, markList) {
215  textObj.markList = markList;
216  textObj.startOffset = textObj.start.physical;
217  return textObj;
218}
219function* textMarkGenerator(ctxManager, wholeContent) {
220  ctxManager.Mark.refresh();
221  for (let textObj of textObjGenerator(ctxManager.Symbol, wholeContent)) {
222    if (!textObj.isSymbol) {
223      yield mkTextMark(textObj, ctxManager.Mark.getUnduplicatedList());
224      continue;
225    }
226    let markList = [];
227    let symbol = textObj.symbolId;
228    let symbolData = ctxManager.Symbol.take(symbol);
229    if (symbolData.view)
230      textObj.view = symbolData.view;
231    if (symbolData.alone)
232      markList.push(`${ctxManager.Mark.take(symbol).mark}`);
233    if (symbolData.closing) {
234      for (let opening of ctxManager.Mark.close(symbol))
235        markList.push(`${ctxManager.Mark.take(opening).mark}-end`);
236    }
237    let symbolUsed = markList.length > 0;
238    markList = markList.concat(ctxManager.Mark.getUnduplicatedList());
239    if (!symbolUsed && symbolData.opening) {
240      ctxManager.Mark.open(symbol);
241      markList.push(`${ctxManager.Mark.take(symbol).mark}-start`);
242    }
243    yield mkTextMark(textObj, markList);
244  }
245}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.