<body>

    <div id="search-container" style="display: none;">
        <div class="search-input-group">
            <div class="input-and-button-wrapper">
                <input type="text" id="partial-search-input" placeholder="あいまいキーワードを入力">
                <button onclick="searchCSV('partial')">あいまい検索</button>
            </div>
        </div>
        <div class="search-input-group">
            <div class="input-and-button-wrapper">
                <input type="text" id="exact-search-input" placeholder="完全一致キーワードを入力">
                <button onclick="searchCSV('exact')">完全一致検索</button>
            </div>
        </div>
    </div>

    <div id="csv-output">
    </div>

    <div id="pagination" style="display: none;">
        <button onclick="prevPage()">前へ</button>
        <span id="page-info"></span>
        <button onclick="nextPage()">次へ</button>
    </div>

    <style>
        /* ここから追加・修正するCSS */

        /* 1. ウェブフォントの定義 */
        /* PMingLiU-ExtBフォントをウェブフォントとして読み込む設定です。 */
        /* PMingLiU-ExtB.ttf ファイルをウェブサーバーの適切な場所に配置してください。 */
        /* 例: /wp-content/themes/cocoon-child-master/fonts/PMingLiU-ExtB.ttf */
        /* 可能であれば、ファイルサイズが小さい .woff2 や .woff 形式も用意すると、読み込みが高速になります。 */
        /* 例えば、フォントコンバーター (例: Transfonter, Font Squirrel の Webfont Generator) を利用して変換できます。 */
        @font-face {
            font-family: 'PMingLiU-ExtB-02'; /* このフォント名でCSSから参照します */
            src: url('/wp-content/themes/cocoon-child-master/fonts/PMingLiU-ExtB-02.woff2') format('woff2'),
                 url('/wp-content/themes/cocoon-child-master/fonts/PMingLiU-ExtB-02.woff') format('woff'),
                 url('/wp-content/themes/cocoon-child-master/fonts/PMingLiU-ExtB-02.ttf') format('truetype');
            font-weight: normal;
            font-style: normal;
            /* font-display: swap; を追加すると、フォントが読み込まれるまでシステムのフォントを表示します */
            font-display: swap;
        }

        /* 2. 漢字(喃字)表記の列にフォントを適用 */
        /* displayOrder配列のインデックス2 (つまり表示上3番目の列) が「漢字(喃字)表記」なので、 */
        /* :nth-child(3) を使ってその列のセルにフォントを適用します。 */
       body .csv-output table td:nth-child(3) {
    font-family: 'PMingLiU-ExtB-02', 'NomNaTong-Regular', 'Noto Sans CJK JP', sans-serif !important;
       }
            /* 複数の代替フォントを指定することで、 */
            /* PMingLiU-ExtBが利用できない場合でも、 */
            /* 次善の表示を試みます。 */
            /* Noto Serif CJK JP や Noto Sans CJK JP は、Google Fontsから利用できる高機能なフォントです。 */
            /* 必要に応じてシステムの日本語フォント(例: 'Hiragino Kaku Gothic ProN', 'Meiryo')なども追加できます。 */
   

        /* 既存のCSSはそのまま */
        /* 検索バーの長さを固定するCSS */
        #partial-search-input,
        #exact-search-input {
            max-width: 50ch;
            width: 100%;
            box-sizing: border-box;
            margin-right: 10px;
            height: 3vh;
        }

        /* 検索ボタンの縦の長さを7割に設定 */
        .search-input-group button {
            height: 3vh;
            padding: 0 15px;
            white-space: nowrap;
            flex-shrink: 0;
        }

        /* 検索インプットの親要素の幅も調整すると、全体がスッキリします */
        .search-input-group {
            margin-bottom: 10px;
        }

        /* 新しいラッパー要素のスタイル */
        .input-and-button-wrapper {
            display: flex;
            align-items: center;
            max-width: calc(50ch + 100px);
        }

        /* 音声ボタンとセル内の文字の間のスペースを確保するためのスタイル */
        .csv-output table td div {
            display: flex;
            align-items: center;
            gap: 10px;
        }

        /* セル内のテキスト表示を1行にし、必要に応じてスクロールバーを表示する */
        .csv-output table td span {
            display: block;
            white-space: nowrap;
            overflow-x: auto;
            -webkit-overflow-scrolling: touch;
            flex-grow: 1;
            scrollbar-width: thin;
            scrollbar-color: grey lightgrey;
        }

        /* Webkit系ブラウザ(Chrome, Safari)でのスクロールバーのスタイル */
        .csv-output table td span::-webkit-scrollbar {
            height: 5px;
        }

        .csv-output table td span::-webkit-scrollbar-track {
            background: #f1f1f1;
        }

        .csv-output table td span::-webkit-scrollbar-thumb {
            background: #888;
            border-radius: 5px;
        }

        .csv-output table td span::-webkit-scrollbar-thumb:hover {
            background: #555;
        }

        /* ハイライト用のスタイル */
        .highlight {
            background-color: yellow;
            font-weight: bold;
        }

        /* 新しい音声ボタンのスタイル */
        .speak-button {
            background-color: #4CAF50;
            color: white;
            border: none;
            padding: 5px 8px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 14px;
            margin: 0;
            cursor: pointer;
            border-radius: 4px;
            line-height: 1;
            flex-shrink: 0;
        }

        .speak-button:hover {
            background-color: #45a049;
        }

        /* 音声列のtdの幅を調整 (既存の音声列) */
        .csv-output table td:nth-child(4) {
            width: 40px;
            text-align: center;
        }

        /* 音声2列のtdの幅を調整 (新しい音声2列) */
        .csv-output table td:nth-child(8) {
            width: 40px;
            text-align: center;
        }

        /* ベトナム語表記の列の幅を調整 */
        .csv-output table td:nth-child(2) {
            min-width: 150px;
        }

    </style>

    <script>
        console.log("スクリプトが読み込まれました。");

        let csvData = [];
        let filteredData = [];
        let currentPage = 1;
        const rowsPerPage = 10;
        const searchContainer = document.getElementById('search-container');
        const partialSearchInput = document.getElementById('partial-search-input');
        const exactSearchInput = document.getElementById('exact-search-input');
        const csvOutputDiv = document.getElementById('csv-output');
        const paginationDiv = document.getElementById('pagination');
        const pageInfoSpan = document.getElementById('page-info');
        // 無声調アルファベットの列(インデックス2)をヘッダーから除外
        // 変更点1: headers配列に「音声」と「音声2」を追加し、表示順序を調整
        const headers = ["漢字語/固有語/混合語", "ベトナム語表記", "漢字(喃字)表記", "音声", "対義語", "日本語", "ひらがな/アルファベット", "音声2", "類語/ジャンル"];
        const voicelessAlphabetColumnIndex = 2; // 無声調アルファベット列の元のインデックス
        let totalRowCount = 0;
        const vietnameseColumnIndex = 1; // 元のCSVにおけるベトナム語表記の列インデックス
        const hiraganaAlphabetColumnIndex = 6; // 元のCSVにおけるひらがな/アルファベットの列インデックス

        let speechUtterance = null;
        let speechUtterance2 = null; // 新しい音声合成インスタンス
        let currentSearchType = ''; // 現在の検索タイプを保持する変数

        if ('speechSynthesis' in window) {
            console.log("音声合成APIが利用可能です。");
            speechUtterance = new SpeechSynthesisUtterance();
            speechUtterance.lang = 'vi-VN';

            speechUtterance2 = new SpeechSynthesisUtterance(); // 音声2用のインスタンス
            speechUtterance2.lang = 'ja-JP'; // 日本語を設定

            window.speechSynthesis.onvoiceschanged = function () {
                console.log("音声リストが変更されました。");
                const voices = window.speechSynthesis.getVoices();
                let vietnameseVoice = null;
                let japaneseVoice = null; // 日本語の音声

                for (let i = 0; i < voices.length; i++) {
                    if (voices[i].lang === 'vi-VN') {
                        if (voices[i].name.toLowerCase().includes('female')) {
                            vietnameseVoice = voices[i];
                            break;
                        }
                    } else if (voices[i].lang === 'vi-VN') {
                        vietnameseVoice = voices[i];
                    }
                    if (voices[i].lang === 'ja-JP') { // 日本語の音声を検索
                        if (voices[i].name.toLowerCase().includes('female')) { // 女性の声を優先
                            japaneseVoice = voices[i];
                            // 女性の声が見つかったら、より自然な声が見つかるまで検索を続けるか、ここでbreakしてもよい
                        } else if (!japaneseVoice) { // まだ日本語音声が見つかっていない場合、最初の日本語音声を候補にする
                            japaneseVoice = voices[i];
                        }
                    }
                }
                if (vietnameseVoice) {
                    speechUtterance.voice = vietnameseVoice;
                } else {
                    console.log('ベトナム語の女性の声が見つかりませんでした。');
                }
                if (japaneseVoice) {
                    speechUtterance2.voice = japaneseVoice;
                } else {
                    console.log('日本語の声が見つかりませんでした。');
                }
            };
        } else {
            console.log("音声合成APIは利用できません。");
            alert('このブラウザは音声読み上げに対応していません。');
        }

        // CSVファイルを自動的に読み込む
        const csvFileUrl = '/wp-content/themes/cocoon-child-master/read_csv.php'; // あなたのCSVファイルの正確なURLに置き換えてください

        fetch(csvFileUrl)
            .then(response => {
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status} - Could not load CSV from: ${csvFileUrl}`);
                }
                return response.text();
            })
            .then(data => {
                console.log("CSVファイルが自動的に読み込まれました。");
                const lines = data.trim().split('\n');
                console.log("読み込まれた行数:", lines.length);
                if (lines.length > 0) {
                    console.log("CSVデータが存在します。");
                    csvData = lines.map(line => parseCSVLine(line));
                    console.log("CSVデータがパースされました。最初の行:", csvData[0]);
                    searchContainer.style.display = 'block';
                    console.log("検索コンテナが表示されました。");
                    filteredData = csvData.slice(1); // ヘッダー行を除外
                    currentPage = 1;
                    totalRowCount = csvData.length - 1; // ヘッダー行を除いた実際のデータ行数
                    displayCSV(filteredData, '', ''); // 初期表示はハイライトなし
                    console.log("displayCSV関数が呼び出されました。");
                    updatePagination();
                } else {
                    console.log("CSVファイルが空です。");
                    alert('CSVファイルが空です。');
                    searchContainer.style.display = 'none';
                    csvOutputDiv.innerHTML = '';
                    paginationDiv.style.display = 'none';
                    csvData = [];
                    filteredData = [];
                    currentPage = 1;
                    totalRowCount = 0;
                    displayCSV([], '', '');
                }
            })
            .catch(error => {
                console.error("CSVファイルの読み込みエラー:", error);
                alert(`CSVファイルの読み込みに失敗しました。ファイルが正しい場所にあるか、またはアクセス可能か確認してください: ${csvFileUrl}`);
                searchContainer.style.display = 'none';
                csvOutputDiv.innerHTML = '';
                paginationDiv.style.display = 'none';
            });


        partialSearchInput.addEventListener('keypress', function (event) {
            if (event.key === 'Enter') {
                searchCSV('partial');
            }
        });

        exactSearchInput.addEventListener('keypress', function (event) {
            if (event.key === 'Enter') {
                searchCSV('exact');
            }
        });

        function parseCSVLine(line) {
            const values = [];
            let currentValue = '';
            let inQuotes = false;
            for (let i = 0; i < line.length; i++) {
                const char = line[i];
                if (char === '"') {
                    inQuotes = !inQuotes;
                } else if (char === ',') {
                    if (!inQuotes) {
                        values.push(currentValue.trim());
                        currentValue = '';
                    } else {
                        currentValue += char;
                    }
                } else {
                    currentValue += char;
                }
            }
            values.push(currentValue.trim());
            return values;
        }

        function speakVietnamese(text) {
            if (speechUtterance) {
                if (window.speechSynthesis) {
                    speechUtterance.text = text;
                    speechUtterance.rate = 0.6;
                    window.speechSynthesis.speak(speechUtterance);
                }
            }
        }

        function speakJapanese(text) {
            if (speechUtterance2) {
                if (window.speechSynthesis) {
                    speechUtterance2.text = text;
                    speechUtterance2.rate = 1.0; // 日本語は通常の速度で
                    window.speechSynthesis.speak(speechUtterance2);
                }
            }
        }

        function displayCSV(data, partialSearchTerm, exactSearchTerm) {
            console.log("displayCSVが開始されました。表示するデータ件数:", data.length);
            const startIndex = (currentPage - 1) * rowsPerPage;
            const endIndex = startIndex + rowsPerPage;
            const currentPageData = data.slice(startIndex, endIndex);
            const currentFilteredRowCount = filteredData.length; // 検索結果の総件数

            const table = document.createElement('table');
            table.id = 'csv-table';
            console.log("テーブル要素が作成されました:", table);

            const thead = table.createTHead();
            const headerRow = thead.insertRow();
            // 変更点2: ヘッダーをループして表示
            headers.forEach(header => {
                const th = document.createElement('th');
                th.textContent = header;
                headerRow.appendChild(th);
            });

            const tbody = table.createTBody();
            currentPageData.forEach(row => {
                const dataRow = tbody.insertRow();
                // 表示する列のインデックスと元のCSVの列インデックスのマッピング
                // 元のCSVの列: 0:漢字語/固有語/混合語, 1:ベトナム語表記, 2:無声調..., 3:漢字(喃字)表記, 4:対義語..., 5:日本語, 6:ひらがな/アルファベット, 7:類語/ジャンル
                // 表示順序: 0, 1, 3(漢字...), '音声', 4(対義語...), 5(日本語), 6(ひらがな), '音声2', 7(類語)
                const displayOrder = [0, 1, 3, '音声', 4, 5, 6, '音声2', 7];

                displayOrder.forEach((displayColumn, displayIdx) => {
                    const td = document.createElement('td');
                    let cellContent = '';
                    let originalCellData = ''; // 音声読み上げのために元のセルデータを保持

                    if (displayColumn === '音声') {
                        // 変更点3: 「音声」列に音声ボタンを追加
                        if ('speechSynthesis' in window) {
                            const speakButton = document.createElement('button');
                            speakButton.classList.add('speak-button');
                            speakButton.textContent = '🔊';
                            // ベトナム語表記の列の元のデータ (元のCSVのインデックス1) を取得
                            const vietnameseTextForSpeech = row[vietnameseColumnIndex] || '';
                            speakButton.onclick = function () {
                                speakVietnamese(vietnameseTextForSpeech); // 元のテキストを読み上げる
                            };
                            td.appendChild(speakButton);
                        }
                    } else if (displayColumn === '音声2') { // 新しく追加した「音声2」列
                        if ('speechSynthesis' in window) {
                            const speakButton2 = document.createElement('button');
                            speakButton2.classList.add('speak-button');
                            speakButton2.textContent = '🔊';
                            // ひらがな/アルファベットの列の元のデータ (元のCSVのインデックス6) を取得
                            const hiraganaAlphabetTextForSpeech = row[hiraganaAlphabetColumnIndex] || '';
                            speakButton2.onclick = function () {
                                speakJapanese(hiraganaAlphabetTextForSpeech); // 日本語を読み上げる
                            };
                            td.appendChild(speakButton2);
                        }
                    } else {
                        const originalIndex = displayColumn;
                        if (originalIndex >= row.length) {
                            // CSVのデータが不足している場合
                            cellContent = '';
                            originalCellData = '';
                        } else {
                            cellContent = row[originalIndex];
                            originalCellData = row[originalIndex]; // 元のデータを保持
                        }

                        let termToHighlight = '';

                        // どの検索タイプで検索が行われたかに応じてハイライトするキーワードを設定
                        if (currentSearchType === 'partial' && partialSearchTerm) {
                            termToHighlight = partialSearchTerm;
                        } else if (currentSearchType === 'exact' && exactSearchTerm) {
                            termToHighlight = exactSearchTerm;
                        }

                        if (termToHighlight) { // 検索キーワードが存在する場合のみハイライトを適用
                            cellContent = highlightText(cellContent, termToHighlight);
                        }

                        const cellText = document.createElement('span');
                        cellText.innerHTML = cellContent; // innerHTMLでハイライトを適用

                        // ベトナム語表記の列にのみテキストと音声ボタンをdivで囲む
                        if (displayColumn === vietnameseColumnIndex) {
                            const container = document.createElement('div');
                            container.appendChild(cellText); // ハイライト済みのテキストを追加
                            td.appendChild(container);
                        } else {
                            td.appendChild(cellText);
                        }
                    }
                    dataRow.appendChild(td);
                });
            });

            const tfoot = table.createTFoot();
            const footerRow = tfoot.insertRow();
            const footerCell = footerRow.insertCell();
            footerCell.colSpan = headers.length; // 表示されているヘッダーの数にcolSpanを合わせる
            footerCell.style.textAlign = 'left';
            footerCell.innerHTML = `総行数: ${totalRowCount} 行 <span style="margin-left: 20px;">該当行数: ${currentFilteredRowCount} 行</span>`;

            csvOutputDiv.innerHTML = '';
            csvOutputDiv.appendChild(table);
            console.log("テーブルがcsvOutputDivに追加されました。");
        }

        // highlightText関数は変更なし
        function highlightText(text, term) {
            if (!term) {
                return text;
            }
            // termを正規表現でエスケープ
            const escapedTerm = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
            const regex = new RegExp(escapedTerm, 'gi');
            return text.replace(regex, '<span class="highlight">$&</span>');
        }

        function searchCSV(searchTypeParam) {
            currentSearchType = searchTypeParam; // 現在の検索タイプを保存
            let partialTerm = partialSearchInput.value.toLowerCase().trim();
            let exactTerm = exactSearchInput.value.toLowerCase().trim();

            filteredData = csvData.slice(1).filter(row => {
                let partialMatch = false;
                let exactMatch = false;

                // 検索対象の列を定義 (元のCSVの列インデックスに基づきます)
                const searchableColumns = [0, 1, 2, 3, 4, 5, 6, 7];

                if (partialTerm !== "") {
                    partialMatch = searchableColumns.some(colIndex => {
                        return row[colIndex] && row[colIndex].toLowerCase().includes(partialTerm);
                    });
                } else {
                    partialMatch = true;
                }

                if (exactTerm !== "") {
                    exactMatch = searchableColumns.some(colIndex => {
                        return row[colIndex] && row[colIndex].toLowerCase() === exactTerm;
                    });
                } else {
                    exactMatch = true;
                }

                if (searchTypeParam === 'partial') {
                    return partialMatch;
                } else if (searchTypeParam === 'exact') {
                    return exactMatch;
                }
                return false;
            });

            currentPage = 1;
            displayCSV(filteredData, partialTerm, exactTerm);
            updatePagination();
        }

        function updatePagination() {
            const totalPages = Math.ceil(filteredData.length / rowsPerPage);
            if (totalPages > 1) {
                paginationDiv.style.display = 'block';
                pageInfoSpan.textContent = `${currentPage} / ${totalPages} ページ`;
            } else {
                paginationDiv.style.display = 'none';
            }
        }

        function nextPage() {
            const totalPages = Math.ceil(filteredData.length / rowsPerPage);
            if (currentPage < totalPages) {
                currentPage++;
                let partialTerm = partialSearchInput.value.toLowerCase();
                let exactTerm = exactSearchInput.value.toLowerCase();
                displayCSV(filteredData, partialTerm, exactTerm);
                updatePagination();
            }
        }

        function prevPage() {
            if (currentPage > 1) {
                currentPage--;
                let partialTerm = partialSearchInput.value.toLowerCase();
                let exactTerm = exactSearchInput.value.toLowerCase();
                displayCSV(filteredData, partialTerm, exactTerm);
                updatePagination();
            }
        }

        function updateTotalRowCount(count) {
            totalRowCount = count;
        }
    </script>
</body>

コメント