end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

sqlite fts4 + sql.js によるブラウザでの全文検索 (zipデータの場合)

sqlite fts4 + sql.js によるブラウザでの全文検索 - end0tknr's kipple - web写経開発

前回の上記entryでは、sqliteのバイナリデータ(.db)を使用しましたが、 .dbでは容量が大きい為、更にzip化したものを扱うようにしてみました。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    body {font-family: "Helvetica Neue", Arial,"Hiragino Kaku Gothic ProN",
                       "Hiragino Sans", Meiryo, sans-serif;
          color: #555;}
    a:visited { color: #00f; }
    /* ヘッダ部の表示位置固定 */
    th     { position: sticky;
             top: 0;
             background-color:#b0e0e6;
             font-weight: normal;}
    table thead th:nth-child(1){width: 50px;}
    table thead th:nth-child(2){width: 65px;}
    table thead th:nth-child(3){width: 100px; }
    table thead th:nth-child(4){width: 520px;}
    table thead th:nth-child(5){width: 80px;}
    table tbody td:nth-child(1){text-align:center;}
    table tbody td:nth-child(2){text-align:center;}
    table tbody td:nth-child(3){text-align:center;}
    table tbody td:nth-child(5){text-align:center;}
    
    table  { border-collapse: collapse; }
    th, td { border : 1px solid #888;
             padding: 1px;              }
    #toast_msg{color: #ff4500;          }
    #toast_msg.hide {display: none;     }
    h1 {margin:5px 0; font-size:1.5em;
        font-family:"HG丸ゴシックM-PRO";}
    .note {font-size:small;}
    #find_str {width:500px;}
    #help_link_cotainer { color: #ff4500;}
  </style>
</head>


<body cz-shortcut-listen="true">
  <div>
    図面内に記載されている文字で図番を抽出
  </div>

  <select id="zumen_type">
    <option value="sekou_manual">施工マニュアル</option>
  </select>
  <input
    type="text" id="find_str" placeholder="検索キーワードを指定してください">
  <button type="button" id="find_btn">検索</button>

  <div id="toast_msg" class="hide">検索結果がある状態でご利用ください</div>
  
  <table id="find_result">
    <thead>
      <tr>
        <th></th>
        <th></th>
        <th>図番-付番</th>
        <th>図面名称</th>
        <th>作成日</th>
      </tr>
    </thead>
    <tbody>
  </tbody>
  </table>

  <template id="tbody_tr">
    <tr>
      <td></td><td></td>
      <td></td><td></td><td></td>
    </tr>
  </template>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.2/sql-wasm.js">
  </script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js">
  </script>
  <script src="./find_zumen2.js"></script>

</body></html>
'use strict';

class FindHinban {
    constructor() {}

    download_zip = async(url)=>{
    const response = await fetch(url);
    if (! response.ok ){
        return undefined;
    }
    let jszip = new JSZip();
    let res_array_buf = response.arrayBuffer();
    let zip = await jszip.loadAsync( res_array_buf );
    return zip;
    }

    init_window =async ()=> {
    const SQL = await initSqlJs({
            locateFile:file=>
        `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.2/${file}`
    });

    let zip = await this.download_zip("sekou_manual_full_texts.zip");
    for (const filename in zip.files) {
            let zip_content = await zip.files[filename].async("uint8array");
        this.db = new SQL.Database( zip_content );
        break;
    }
    
        document.querySelector("#find_btn").addEventListener('click',()=>{
            this.find_hinban();
        });
        document.querySelector("#find_str").addEventListener('keypress',(e)=>{
            if (e.keyCode == 13) { // ENTER
                this.find_hinban();
            }
        });
    }
    
    find_hinban=()=>{
        let find_str = document.querySelector("#find_str").value;

        find_str = this.validate_find_str(find_str);
        if(! find_str ){
            this.toast_msg("検索キーワードを指定してください");
            return;
        }

    let sql = "SELECT * FROM zumen_words WHERE words MATCH (?)";
    let stmt = this.db.prepare(sql);
    stmt.bind([find_str]);
    console.log( stmt );
    
        let tbody_elm = document.querySelector("#find_result tbody");
        let tr_tmpl = document.querySelector("#tbody_tr");
    tbody_elm.innerHTML = "";
    
        let i = 0;
    while( stmt.step() ) {
        i++;
            let row = stmt.getAsObject();
            var tr_elm = tr_tmpl.content.cloneNode(true);
        
        //console.log( i, row );
            let td = tr_elm.querySelectorAll("td");
            td[0].textContent = i++;
            td[2].textContent = row["file"];
            td[3].textContent = row["name_kanji"];
            td[4].textContent = row["create_day"];
            tbody_elm.appendChild(tr_elm);
    }
    if(i==0){
            this.toast_msg("見つかりませんでした");
            return;
    }

    stmt.free();
    }

    disp_find_result=(res_tsvs)=>{
        let tbody_elm = document.querySelector("#find_result tbody");
        var clone_tbody = tbody_elm.cloneNode(false);
        tbody_elm.parentNode.replaceChild(clone_tbody, tbody_elm);
        
        tbody_elm = document.querySelector("#find_result tbody");

        let tsvs = res_tsvs.split("\n");
        if ( res_tsvs.length == 0){
            this.toast_msg("検索キーワードに一致するものは見つかりませんでした");
            return;
        }
        
        let tr_tmpl = document.querySelector("#tbody_tr");

        let i = 1;
        for(let tsv of tsvs){
            let disp_cols = tsv.split("\t");
            var tr_elm = tr_tmpl.content.cloneNode(true);
            var td = tr_elm.querySelectorAll("td");
            td[0].textContent = i++;
            td[2].textContent = disp_cols[0];
            td[3].textContent = disp_cols[1];
            td[4].textContent = disp_cols[2];
            td[5].textContent = disp_cols[4];
            td[6].textContent = disp_cols[5].substring(2);
            td[7].textContent = disp_cols[6];
            tbody_elm.appendChild(tr_elm);
        }

        let btn_elms = document.querySelectorAll("#find_result tbody button");
        for(let btn_elm of btn_elms ){
            btn_elm.addEventListener("click",(e)=>{ this.view_scan(e); });
        }
    }

    view_scan=(e)=>{
        let tr_elm = e.target.parentNode.parentNode;
        let td_elms = tr_elm.querySelectorAll("td");
        let zuban = td_elms[2].textContent.trim();
        let req_url = scan_base_url + zuban;
        location.href = req_url;
    }
    
    toast_msg=(msg)=>{
        let msg_container = document.querySelector("#toast_msg");
        msg_container.innerHTML = msg;
        
        msg_container.className = msg_container.className.replace("hide","");
        setTimeout(()=>{ msg_container.className="hide";},4500);
    }

    validate_find_str=(org_find_str)=>{
        org_find_str = org_find_str.trim();
        if ( org_find_str.length == 0 ){
            return undefined;
        }
        return org_find_str;
    }
}

let find_hinban = new FindHinban();
find_hinban.init_window();