xref: /aosp_15_r20/external/tinyxml2/docs/search/search.js (revision 7485b22521f577cf944e5687361548d8993d8d2c)
1/*
2 @licstart  The following is the entire license notice for the JavaScript code in this file.
3
4 The MIT License (MIT)
5
6 Copyright (C) 1997-2020 by Dimitri van Heesch
7
8 Permission is hereby granted, free of charge, to any person obtaining a copy of this software
9 and associated documentation files (the "Software"), to deal in the Software without restriction,
10 including without limitation the rights to use, copy, modify, merge, publish, distribute,
11 sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
12 furnished to do so, subject to the following conditions:
13
14 The above copyright notice and this permission notice shall be included in all copies or
15 substantial portions of the Software.
16
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
18 BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
20 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
23 @licend  The above is the entire license notice for the JavaScript code in this file
24 */
25const SEARCH_COOKIE_NAME = ''+'search_grp';
26
27const searchResults = new SearchResults();
28
29/* A class handling everything associated with the search panel.
30
31   Parameters:
32   name - The name of the global variable that will be
33          storing this instance.  Is needed to be able to set timeouts.
34   resultPath - path to use for external files
35*/
36function SearchBox(name, resultsPath, extension) {
37  if (!name || !resultsPath) {  alert("Missing parameters to SearchBox."); }
38  if (!extension || extension == "") { extension = ".html"; }
39
40  function getXPos(item) {
41    let x = 0;
42    if (item.offsetWidth) {
43      while (item && item!=document.body) {
44        x   += item.offsetLeft;
45        item = item.offsetParent;
46      }
47    }
48    return x;
49  }
50
51  function getYPos(item) {
52    let y = 0;
53    if (item.offsetWidth) {
54      while (item && item!=document.body) {
55        y   += item.offsetTop;
56        item = item.offsetParent;
57      }
58    }
59    return y;
60  }
61
62  // ---------- Instance variables
63  this.name                  = name;
64  this.resultsPath           = resultsPath;
65  this.keyTimeout            = 0;
66  this.keyTimeoutLength      = 500;
67  this.closeSelectionTimeout = 300;
68  this.lastSearchValue       = "";
69  this.lastResultsPage       = "";
70  this.hideTimeout           = 0;
71  this.searchIndex           = 0;
72  this.searchActive          = false;
73  this.extension             = extension;
74
75  // ----------- DOM Elements
76
77  this.DOMSearchField              = () => document.getElementById("MSearchField");
78  this.DOMSearchSelect             = () => document.getElementById("MSearchSelect");
79  this.DOMSearchSelectWindow       = () => document.getElementById("MSearchSelectWindow");
80  this.DOMPopupSearchResults       = () => document.getElementById("MSearchResults");
81  this.DOMPopupSearchResultsWindow = () => document.getElementById("MSearchResultsWindow");
82  this.DOMSearchClose              = () => document.getElementById("MSearchClose");
83  this.DOMSearchBox                = () => document.getElementById("MSearchBox");
84
85  // ------------ Event Handlers
86
87  // Called when focus is added or removed from the search field.
88  this.OnSearchFieldFocus = function(isActive) {
89    this.Activate(isActive);
90  }
91
92  this.OnSearchSelectShow = function() {
93    const searchSelectWindow = this.DOMSearchSelectWindow();
94    const searchField        = this.DOMSearchSelect();
95
96    const left = getXPos(searchField);
97    const top  = getYPos(searchField) + searchField.offsetHeight;
98
99    // show search selection popup
100    searchSelectWindow.style.display='block';
101    searchSelectWindow.style.left =  left + 'px';
102    searchSelectWindow.style.top  =  top  + 'px';
103
104    // stop selection hide timer
105    if (this.hideTimeout) {
106      clearTimeout(this.hideTimeout);
107      this.hideTimeout=0;
108    }
109    return false; // to avoid "image drag" default event
110  }
111
112  this.OnSearchSelectHide = function() {
113    this.hideTimeout = setTimeout(this.CloseSelectionWindow.bind(this),
114                                  this.closeSelectionTimeout);
115  }
116
117  // Called when the content of the search field is changed.
118  this.OnSearchFieldChange = function(evt) {
119    if (this.keyTimeout) { // kill running timer
120      clearTimeout(this.keyTimeout);
121      this.keyTimeout = 0;
122    }
123
124    const e = evt ? evt : window.event; // for IE
125    if (e.keyCode==40 || e.keyCode==13) {
126      if (e.shiftKey==1) {
127        this.OnSearchSelectShow();
128        const win=this.DOMSearchSelectWindow();
129        for (let i=0;i<win.childNodes.length;i++) {
130          const child = win.childNodes[i]; // get span within a
131          if (child.className=='SelectItem') {
132            child.focus();
133            return;
134          }
135        }
136        return;
137      } else {
138        const elem = searchResults.NavNext(0);
139        if (elem) elem.focus();
140      }
141    } else if (e.keyCode==27) { // Escape out of the search field
142      e.stopPropagation();
143      this.DOMSearchField().blur();
144      this.DOMPopupSearchResultsWindow().style.display = 'none';
145      this.DOMSearchClose().style.display = 'none';
146      this.lastSearchValue = '';
147      this.Activate(false);
148      return;
149    }
150
151    // strip whitespaces
152    const searchValue = this.DOMSearchField().value.replace(/ +/g, "");
153
154    if (searchValue != this.lastSearchValue) { // search value has changed
155      if (searchValue != "") { // non-empty search
156        // set timer for search update
157        this.keyTimeout = setTimeout(this.Search.bind(this), this.keyTimeoutLength);
158      } else { // empty search field
159        this.DOMPopupSearchResultsWindow().style.display = 'none';
160        this.DOMSearchClose().style.display = 'none';
161        this.lastSearchValue = '';
162      }
163    }
164  }
165
166  this.SelectItemCount = function() {
167    let count=0;
168    const win=this.DOMSearchSelectWindow();
169    for (let i=0;i<win.childNodes.length;i++) {
170      const child = win.childNodes[i]; // get span within a
171      if (child.className=='SelectItem') {
172        count++;
173      }
174    }
175    return count;
176  }
177
178  this.GetSelectionIdByName = function(name) {
179    let j=0;
180    const win=this.DOMSearchSelectWindow();
181    for (let i=0;i<win.childNodes.length;i++) {
182      const child = win.childNodes[i];
183      if (child.className=='SelectItem') {
184        if (child.childNodes[1].nodeValue==name) {
185          return j;
186        }
187        j++;
188      }
189    }
190    return 0;
191  }
192
193  this.SelectItemSet = function(id) {
194    let j=0;
195    const win=this.DOMSearchSelectWindow();
196    for (let i=0;i<win.childNodes.length;i++) {
197      const child = win.childNodes[i]; // get span within a
198      if (child.className=='SelectItem') {
199        const node = child.firstChild;
200        if (j==id) {
201          node.innerHTML='&#8226;';
202          Cookie.writeSetting(SEARCH_COOKIE_NAME, child.childNodes[1].nodeValue, 0)
203        } else {
204          node.innerHTML='&#160;';
205        }
206        j++;
207      }
208    }
209  }
210
211  // Called when an search filter selection is made.
212  // set item with index id as the active item
213  this.OnSelectItem = function(id) {
214    this.searchIndex = id;
215    this.SelectItemSet(id);
216    const searchValue = this.DOMSearchField().value.replace(/ +/g, "");
217    if (searchValue!="" && this.searchActive) { // something was found -> do a search
218      this.Search();
219    }
220  }
221
222  this.OnSearchSelectKey = function(evt) {
223    const e = (evt) ? evt : window.event; // for IE
224    if (e.keyCode==40 && this.searchIndex<this.SelectItemCount()) { // Down
225      this.searchIndex++;
226      this.OnSelectItem(this.searchIndex);
227    } else if (e.keyCode==38 && this.searchIndex>0) { // Up
228      this.searchIndex--;
229      this.OnSelectItem(this.searchIndex);
230    } else if (e.keyCode==13 || e.keyCode==27) {
231      e.stopPropagation();
232      this.OnSelectItem(this.searchIndex);
233      this.CloseSelectionWindow();
234      this.DOMSearchField().focus();
235    }
236    return false;
237  }
238
239  // --------- Actions
240
241  // Closes the results window.
242  this.CloseResultsWindow = function() {
243    this.DOMPopupSearchResultsWindow().style.display = 'none';
244    this.DOMSearchClose().style.display = 'none';
245    this.Activate(false);
246  }
247
248  this.CloseSelectionWindow = function() {
249    this.DOMSearchSelectWindow().style.display = 'none';
250  }
251
252  // Performs a search.
253  this.Search = function() {
254    this.keyTimeout = 0;
255
256    // strip leading whitespace
257    const searchValue = this.DOMSearchField().value.replace(/^ +/, "");
258
259    const code = searchValue.toLowerCase().charCodeAt(0);
260    let idxChar = searchValue.substr(0, 1).toLowerCase();
261    if ( 0xD800 <= code && code <= 0xDBFF && searchValue > 1) { // surrogate pair
262      idxChar = searchValue.substr(0, 2);
263    }
264
265    let jsFile;
266    let idx = indexSectionsWithContent[this.searchIndex].indexOf(idxChar);
267    if (idx!=-1) {
268      const hexCode=idx.toString(16);
269      jsFile = this.resultsPath + indexSectionNames[this.searchIndex] + '_' + hexCode + '.js';
270    }
271
272    const loadJS = function(url, impl, loc) {
273      const scriptTag = document.createElement('script');
274      scriptTag.src = url;
275      scriptTag.onload = impl;
276      scriptTag.onreadystatechange = impl;
277      loc.appendChild(scriptTag);
278    }
279
280    const domPopupSearchResultsWindow = this.DOMPopupSearchResultsWindow();
281    const domSearchBox = this.DOMSearchBox();
282    const domPopupSearchResults = this.DOMPopupSearchResults();
283    const domSearchClose = this.DOMSearchClose();
284    const resultsPath = this.resultsPath;
285
286    const handleResults = function() {
287      document.getElementById("Loading").style.display="none";
288      if (typeof searchData !== 'undefined') {
289        createResults(resultsPath);
290        document.getElementById("NoMatches").style.display="none";
291      }
292
293      if (idx!=-1) {
294        searchResults.Search(searchValue);
295      } else { // no file with search results => force empty search results
296        searchResults.Search('====');
297      }
298
299      if (domPopupSearchResultsWindow.style.display!='block') {
300        domSearchClose.style.display = 'inline-block';
301        let left = getXPos(domSearchBox) + 150;
302        let top  = getYPos(domSearchBox) + 20;
303        domPopupSearchResultsWindow.style.display = 'block';
304        left -= domPopupSearchResults.offsetWidth;
305        const maxWidth  = document.body.clientWidth;
306        const maxHeight = document.body.clientHeight;
307        let width = 300;
308        if (left<10) left=10;
309        if (width+left+8>maxWidth) width=maxWidth-left-8;
310        let height = 400;
311        if (height+top+8>maxHeight) height=maxHeight-top-8;
312        domPopupSearchResultsWindow.style.top     = top  + 'px';
313        domPopupSearchResultsWindow.style.left    = left + 'px';
314        domPopupSearchResultsWindow.style.width   = width + 'px';
315        domPopupSearchResultsWindow.style.height  = height + 'px';
316      }
317    }
318
319    if (jsFile) {
320      loadJS(jsFile, handleResults, this.DOMPopupSearchResultsWindow());
321    } else {
322      handleResults();
323    }
324
325    this.lastSearchValue = searchValue;
326  }
327
328  // -------- Activation Functions
329
330  // Activates or deactivates the search panel, resetting things to
331  // their default values if necessary.
332  this.Activate = function(isActive) {
333    if (isActive || // open it
334      this.DOMPopupSearchResultsWindow().style.display == 'block'
335    ) {
336      this.DOMSearchBox().className = 'MSearchBoxActive';
337      this.searchActive = true;
338    } else if (!isActive) { // directly remove the panel
339      this.DOMSearchBox().className = 'MSearchBoxInactive';
340      this.searchActive             = false;
341      this.lastSearchValue          = ''
342      this.lastResultsPage          = '';
343      this.DOMSearchField().value   = '';
344    }
345  }
346}
347
348// -----------------------------------------------------------------------
349
350// The class that handles everything on the search results page.
351function SearchResults() {
352
353  function convertToId(search) {
354    let result = '';
355    for (let i=0;i<search.length;i++) {
356      const c = search.charAt(i);
357      const cn = c.charCodeAt(0);
358      if (c.match(/[a-z0-9\u0080-\uFFFF]/)) {
359        result+=c;
360      } else if (cn<16) {
361        result+="_0"+cn.toString(16);
362      } else {
363        result+="_"+cn.toString(16);
364      }
365    }
366    return result;
367  }
368
369  // The number of matches from the last run of <Search()>.
370  this.lastMatchCount = 0;
371  this.lastKey = 0;
372  this.repeatOn = false;
373
374  // Toggles the visibility of the passed element ID.
375  this.FindChildElement = function(id) {
376    const parentElement = document.getElementById(id);
377    let element = parentElement.firstChild;
378
379    while (element && element!=parentElement) {
380      if (element.nodeName.toLowerCase() == 'div' && element.className == 'SRChildren') {
381        return element;
382      }
383
384      if (element.nodeName.toLowerCase() == 'div' && element.hasChildNodes()) {
385        element = element.firstChild;
386      } else if (element.nextSibling) {
387        element = element.nextSibling;
388      } else {
389        do {
390          element = element.parentNode;
391        }
392        while (element && element!=parentElement && !element.nextSibling);
393
394        if (element && element!=parentElement) {
395          element = element.nextSibling;
396        }
397      }
398    }
399  }
400
401  this.Toggle = function(id) {
402    const element = this.FindChildElement(id);
403    if (element) {
404      if (element.style.display == 'block') {
405        element.style.display = 'none';
406      } else {
407        element.style.display = 'block';
408      }
409    }
410  }
411
412  // Searches for the passed string.  If there is no parameter,
413  // it takes it from the URL query.
414  //
415  // Always returns true, since other documents may try to call it
416  // and that may or may not be possible.
417  this.Search = function(search) {
418    if (!search) { // get search word from URL
419      search = window.location.search;
420      search = search.substring(1);  // Remove the leading '?'
421      search = unescape(search);
422    }
423
424    search = search.replace(/^ +/, ""); // strip leading spaces
425    search = search.replace(/ +$/, ""); // strip trailing spaces
426    search = search.toLowerCase();
427    search = convertToId(search);
428
429    const resultRows = document.getElementsByTagName("div");
430    let matches = 0;
431
432    let i = 0;
433    while (i < resultRows.length) {
434      const row = resultRows.item(i);
435      if (row.className == "SRResult") {
436        let rowMatchName = row.id.toLowerCase();
437        rowMatchName = rowMatchName.replace(/^sr\d*_/, ''); // strip 'sr123_'
438
439        if (search.length<=rowMatchName.length &&
440          rowMatchName.substr(0, search.length)==search) {
441          row.style.display = 'block';
442          matches++;
443        } else {
444          row.style.display = 'none';
445        }
446      }
447      i++;
448    }
449    document.getElementById("Searching").style.display='none';
450    if (matches == 0) { // no results
451      document.getElementById("NoMatches").style.display='block';
452    } else { // at least one result
453      document.getElementById("NoMatches").style.display='none';
454    }
455    this.lastMatchCount = matches;
456    return true;
457  }
458
459  // return the first item with index index or higher that is visible
460  this.NavNext = function(index) {
461    let focusItem;
462    for (;;) {
463      const focusName = 'Item'+index;
464      focusItem = document.getElementById(focusName);
465      if (focusItem && focusItem.parentNode.parentNode.style.display=='block') {
466        break;
467      } else if (!focusItem) { // last element
468        break;
469      }
470      focusItem=null;
471      index++;
472    }
473    return focusItem;
474  }
475
476  this.NavPrev = function(index) {
477    let focusItem;
478    for (;;) {
479      const focusName = 'Item'+index;
480      focusItem = document.getElementById(focusName);
481      if (focusItem && focusItem.parentNode.parentNode.style.display=='block') {
482        break;
483      } else if (!focusItem) { // last element
484        break;
485      }
486      focusItem=null;
487      index--;
488    }
489    return focusItem;
490  }
491
492  this.ProcessKeys = function(e) {
493    if (e.type == "keydown") {
494      this.repeatOn = false;
495      this.lastKey = e.keyCode;
496    } else if (e.type == "keypress") {
497      if (!this.repeatOn) {
498        if (this.lastKey) this.repeatOn = true;
499        return false; // ignore first keypress after keydown
500      }
501    } else if (e.type == "keyup") {
502      this.lastKey = 0;
503      this.repeatOn = false;
504    }
505    return this.lastKey!=0;
506  }
507
508  this.Nav = function(evt,itemIndex) {
509    const e  = (evt) ? evt : window.event; // for IE
510    if (e.keyCode==13) return true;
511    if (!this.ProcessKeys(e)) return false;
512
513    if (this.lastKey==38) { // Up
514      const newIndex = itemIndex-1;
515      let focusItem = this.NavPrev(newIndex);
516      if (focusItem) {
517        let child = this.FindChildElement(focusItem.parentNode.parentNode.id);
518        if (child && child.style.display == 'block') { // children visible
519          let n=0;
520          let tmpElem;
521          for (;;) { // search for last child
522            tmpElem = document.getElementById('Item'+newIndex+'_c'+n);
523            if (tmpElem) {
524              focusItem = tmpElem;
525            } else { // found it!
526              break;
527            }
528            n++;
529          }
530        }
531      }
532      if (focusItem) {
533        focusItem.focus();
534      } else { // return focus to search field
535        document.getElementById("MSearchField").focus();
536      }
537    } else if (this.lastKey==40) { // Down
538      const newIndex = itemIndex+1;
539      let focusItem;
540      const item = document.getElementById('Item'+itemIndex);
541      const elem = this.FindChildElement(item.parentNode.parentNode.id);
542      if (elem && elem.style.display == 'block') { // children visible
543        focusItem = document.getElementById('Item'+itemIndex+'_c0');
544      }
545      if (!focusItem) focusItem = this.NavNext(newIndex);
546      if (focusItem)  focusItem.focus();
547    } else if (this.lastKey==39) { // Right
548      const item = document.getElementById('Item'+itemIndex);
549      const elem = this.FindChildElement(item.parentNode.parentNode.id);
550      if (elem) elem.style.display = 'block';
551    } else if (this.lastKey==37) { // Left
552      const item = document.getElementById('Item'+itemIndex);
553      const elem = this.FindChildElement(item.parentNode.parentNode.id);
554      if (elem) elem.style.display = 'none';
555    } else if (this.lastKey==27) { // Escape
556      e.stopPropagation();
557      searchBox.CloseResultsWindow();
558      document.getElementById("MSearchField").focus();
559    } else if (this.lastKey==13) { // Enter
560      return true;
561    }
562    return false;
563  }
564
565  this.NavChild = function(evt,itemIndex,childIndex) {
566    const e  = (evt) ? evt : window.event; // for IE
567    if (e.keyCode==13) return true;
568    if (!this.ProcessKeys(e)) return false;
569
570    if (this.lastKey==38) { // Up
571      if (childIndex>0) {
572        const newIndex = childIndex-1;
573        document.getElementById('Item'+itemIndex+'_c'+newIndex).focus();
574      } else { // already at first child, jump to parent
575        document.getElementById('Item'+itemIndex).focus();
576      }
577    } else if (this.lastKey==40) { // Down
578      const newIndex = childIndex+1;
579      let elem = document.getElementById('Item'+itemIndex+'_c'+newIndex);
580      if (!elem) { // last child, jump to parent next parent
581        elem = this.NavNext(itemIndex+1);
582      }
583      if (elem) {
584        elem.focus();
585      }
586    } else if (this.lastKey==27) { // Escape
587      e.stopPropagation();
588      searchBox.CloseResultsWindow();
589      document.getElementById("MSearchField").focus();
590    } else if (this.lastKey==13) { // Enter
591      return true;
592    }
593    return false;
594  }
595}
596
597function createResults(resultsPath) {
598
599  function setKeyActions(elem,action) {
600    elem.setAttribute('onkeydown',action);
601    elem.setAttribute('onkeypress',action);
602    elem.setAttribute('onkeyup',action);
603  }
604
605  function setClassAttr(elem,attr) {
606    elem.setAttribute('class',attr);
607    elem.setAttribute('className',attr);
608  }
609
610  const results = document.getElementById("SRResults");
611  results.innerHTML = '';
612  searchData.forEach((elem,index) => {
613    const id = elem[0];
614    const srResult = document.createElement('div');
615    srResult.setAttribute('id','SR_'+id);
616    setClassAttr(srResult,'SRResult');
617    const srEntry = document.createElement('div');
618    setClassAttr(srEntry,'SREntry');
619    const srLink = document.createElement('a');
620    srLink.setAttribute('id','Item'+index);
621    setKeyActions(srLink,'return searchResults.Nav(event,'+index+')');
622    setClassAttr(srLink,'SRSymbol');
623    srLink.innerHTML = elem[1][0];
624    srEntry.appendChild(srLink);
625    if (elem[1].length==2) { // single result
626      srLink.setAttribute('href',resultsPath+elem[1][1][0]);
627      srLink.setAttribute('onclick','searchBox.CloseResultsWindow()');
628      if (elem[1][1][1]) {
629       srLink.setAttribute('target','_parent');
630      } else {
631       srLink.setAttribute('target','_blank');
632      }
633      const srScope = document.createElement('span');
634      setClassAttr(srScope,'SRScope');
635      srScope.innerHTML = elem[1][1][2];
636      srEntry.appendChild(srScope);
637    } else { // multiple results
638      srLink.setAttribute('href','javascript:searchResults.Toggle("SR_'+id+'")');
639      const srChildren = document.createElement('div');
640      setClassAttr(srChildren,'SRChildren');
641      for (let c=0; c<elem[1].length-1; c++) {
642        const srChild = document.createElement('a');
643        srChild.setAttribute('id','Item'+index+'_c'+c);
644        setKeyActions(srChild,'return searchResults.NavChild(event,'+index+','+c+')');
645        setClassAttr(srChild,'SRScope');
646        srChild.setAttribute('href',resultsPath+elem[1][c+1][0]);
647        srChild.setAttribute('onclick','searchBox.CloseResultsWindow()');
648        if (elem[1][c+1][1]) {
649         srChild.setAttribute('target','_parent');
650        } else {
651         srChild.setAttribute('target','_blank');
652        }
653        srChild.innerHTML = elem[1][c+1][2];
654        srChildren.appendChild(srChild);
655      }
656      srEntry.appendChild(srChildren);
657    }
658    srResult.appendChild(srEntry);
659    results.appendChild(srResult);
660  });
661}
662
663function init_search() {
664  const results = document.getElementById("MSearchSelectWindow");
665
666  results.tabIndex=0;
667  for (let key in indexSectionLabels) {
668    const link = document.createElement('a');
669    link.setAttribute('class','SelectItem');
670    link.setAttribute('onclick','searchBox.OnSelectItem('+key+')');
671    link.href='javascript:void(0)';
672    link.innerHTML='<span class="SelectionMark">&#160;</span>'+indexSectionLabels[key];
673    results.appendChild(link);
674  }
675
676  const input = document.getElementById("MSearchSelect");
677  const searchSelectWindow = document.getElementById("MSearchSelectWindow");
678  input.tabIndex=0;
679  input.addEventListener("keydown", function(event) {
680    if (event.keyCode==13 || event.keyCode==40) {
681      event.preventDefault();
682      if (searchSelectWindow.style.display == 'block') {
683        searchBox.CloseSelectionWindow();
684      } else {
685        searchBox.OnSearchSelectShow();
686        searchBox.DOMSearchSelectWindow().focus();
687      }
688    }
689  });
690  const name = Cookie.readSetting(SEARCH_COOKIE_NAME,0);
691  const id = searchBox.GetSelectionIdByName(name);
692  searchBox.OnSelectItem(id);
693}
694/* @license-end */
695