概要
スプレッドシートのデータを [ファイル → 共有 → Web出力] から CSV 出力し、テーブルコンテナのデータ要素「data-csv-url」から CSV ファイル URL を読み取り、スプレッドシートから出力できなかった画像データから空白列を1列削除し、その出力できなかった PNG 画像をセルの slug から PNG 画像パス整形して表示し、「data-table-class」からテーブルコンテナにクラス名、さらにスクリプトからセルに「行 – 列番号」クラス名を付与しスタイルする。PNG、SVGデータはディレクトリに保存する。
また、スプレッドシートからCSV変換する際のエスケープを正規表現により解除する。
なお、テーブルヘッダの構成が単純でない場合、CSV を tbody データ部からとし、テーブル構造を確認する意味からも、HTMLには thead を直記述して tbody から CSV 入力する。
icon-awesomes
| 植物 slug | png | svg |
| CSVデータを読み込み中… |
| ファイル slug | png | svg |
| CSVデータを読み込み中… |
| 電気 slug | png | svg |
| CSVデータを読み込み中… |
| ブランド slug | png | svg |
| CSVデータを読み込み中… |
| その他 slug | png | svg |
| CSVデータを読み込み中… |
| その他2 slug | png | svg |
| CSVデータを読み込み中… |
CSVとSVGの共存
本来、CSVは「フラットな表データ」のための形式であり、SVGのような「階層構造を持つリッチなデータ」を入れることを想定していないが、Web 開発の現場では、デザインアセットの管理、データのポータビリティにおいて必要とされ、Web スプレッドシートからCSV変換する際のエスケープを正規表現により解除する。
leaf-solid,,,"<svg xmlns=""http://www.w3.org/2000/svg"" viewBox=""0 0 512 512""><!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d=""M471.3 6.7C477.7 .6 487-1.6 495.6 1.2 505.4 4.5 512 13.7 512 24l0 186.9c0 131.2-108.1 237.1-238.8 237.1-77 0-143.4-49.5-167.5-118.7-35.4 30.8-57.7 76.1-57.7 126.7 0 13.3-10.7 24-24 24S0 469.3 0 456C0 381.1 38.2 315.1 96.1 276.3 131.4 252.7 173.5 240 216 240l80 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-80 0c-39.7 0-77.3 8.8-111 24.5 23.3-70 89.2-120.5 167-120.5 66.4 0 115.8-22.1 148.7-44 19.2-12.8 35.5-28.1 50.7-45.3z""/></svg>"
<!-- leaf.svg -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M471.3 6.7C477.7 .6 487-1.6 495.6 1.2 505.4 4.5 512 13.7 512 24l0 186.9c0 131.2-108.1 237.1-238.8 237.1-77 0-143.4-49.5-167.5-118.7-35.4 30.8-57.7 76.1-57.7 126.7 0 13.3-10.7 24-24 24S0 469.3 0 456C0 381.1 38.2 315.1 96.1 276.3 131.4 252.7 173.5 240 216 240l80 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-80 0c-39.7 0-77.3 8.8-111 24.5 23.3-70 89.2-120.5 167-120.5 66.4 0 115.8-22.1 148.7-44 19.2-12.8 35.5-28.1 50.7-45.3z"/></svg>
- 基本原則と特性の対比
CSV:カンマ(列区切り)、改行(行区切り)、引用符(データ区切り)をによる単純な平面的構造
SVG:XMLベースのテキストであり、内部に大量のカンマ(数値区切り)、改行(可読性)、引用符(データ属性値の囲み)を持つ立体的構造
- 構造的衝突
■ 階層の入れ子:1次元のCSVセル内に、多次元のXMLが閉じ込められる矛盾
■ 記号、デリミタ(区切り文字)の重複:セル区切り文字としてと、データ数値区切りとしてのカンマ、行区切りと可読性としての改行の、データ区切りとしての引用符の衝突
■ 引用符の競合:CSVエスケープ(””)と、SVG属性値(”)の判別困難
- 正規表現によるエスケープ解除( CSVパーサー)
■ 行分割:sフラグと肯定先読みによる、真の改行の特定 引用符外の改行だけを狙う /(".*?"|[^"\r\n]+)+(?=\r?\n|\n|$)/gs
■ sフラグ(dotAll):整形用改行「. 」を文字として読み込み、1レコードを維持する。
■ 肯定先読み:引用符外の改行のみをレコード終端と識別し、物理的な改行と峻別する。
■ 列分割:引用符保護による、SVGパスデータの隔離 二重引用符を突破し、カンマの「隙間」を逃さない
■ 引用符保護 "([^"]*(?:""[^"]*)*)" :”.*?” 内のカンマを隔離する。
■ 肯定先読み・後方参照による「空列の厳密な捕捉」 (?<=,)(?=,)|(?<=,)(?=$)|(?<=^)(?=,)
通常の split(',') では、,,(空データ)が連続した場合にインデックスがずれたり、無視されたりするが、この正規表現は「文字」ではなく、「カンマの間の隙間」をスキャンする。
(?<=,)(?=,):カンマとカンマの間の「無」を捕捉
(?<=,)(?=$):行末のカンマの後の「無」を捕捉
(?<=^)(?=,):行頭のカンマの前の「無」を捕捉
HTML JavaScript コード
<!-- コンテナクラス名 / ファイル URL / テーブルクラス名 を入力 -->
<div
class="csv-table-tbody-loader"
data-csv-url="/wp-content/themes/wp-test/assets/csv/icon-awesome-plants.csv"
data-table-class=""
>
<table class="table-base">
<thead>
<tr><th colspan="3" class="bg-beige">植物</th></tr>
<tr><th>slug</th><th>png</th><th>svg</th></tr>
</thead>
<tbody>
<tr class="loading-row">
<td colspan="3">CSVデータを読み込み中...</td>
</tr>
</tbody>
</table>
</div>
<div
class="csv-table-tbody-loader"
data-csv-url="/wp-content/themes/wp-test/assets/csv/icon-awesome-files.csv"
data-table-class=""
>
<table class="table-base">
<thead>
<tr><th colspan="3" class="bg-beige">ファイル</th></tr>
<tr><th>slug</th><th>png</th><th>svg</th></tr>
</thead>
<tbody>
<tr class="loading-row">
<td colspan="3">CSVデータを読み込み中...</td>
</tr>
</tbody>
</table>
</div>
...
/* CSVデータ読み込み、テーブル生成 */
/* 汎用データ読み込み部 */
(function() {
async function initCsvTableTbody() {
const containers = document.querySelectorAll('.csv-table-tbody-loader');
for (const container of containers) {
const url = container.dataset.csvUrl;
const customClass = container.dataset.tableClass;
const table = container.querySelector('table');
const tbody = container.querySelector('tbody');
if (!url || !tbody) continue;
if (customClass && table) {
table.classList.add(customClass);
}
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Network response was not ok');
const csvText = await response.text();
if (csvText.trim()) {
renderTbody(tbody, csvText);
}
} catch (error) {
console.error('Fetch error:', error);
if (tbody) {
tbody.innerHTML = '<tr><td colspan="3">エラー: データの読み込みに失敗しました。</td></tr>';
}
}
}
}
/* カスタム部:列定義、PNG、空フィールドスキップ指定 */
function renderTbody(tbody, csvData) {
const lines = csvData.match(/(".*?"|[^"\r\n]+)+(?=\r?\n|\n|$)/gs);
if (!lines) return;
tbody.innerHTML = '';
// 列の定義(構造の可視化と保守性の向上)
const COL_INDEX = {
SLUG: 0,
PNG: 1,
SKIP: 2, // [2, 4, 5] 複数スキップの場合
SVG: 3
};
lines.forEach((line, rI) => {
if (!line.trim()) return;
const rNum = rI + 1;
const cells = [];
const parts = line.match(/"([^"]*(?:""[^"]*)*)"|([^,]+)|(?<=,)(?=,)|(?<=,)(?=$)|(?<=^)(?=,)/g) || [];
parts.forEach(p => {
if (p.startsWith('"') && p.endsWith('"')) {
cells.push(p.substring(1, p.length - 1).replace(/""/g, '"'));
} else {
cells.push(p);
}
});
// 定義に基づいたデータ抽出
const slug = (cells[COL_INDEX.SLUG] || "").trim();
const cleanSvg = cells[COL_INDEX.SVG] || "";
const bTr = document.createElement('tr');
bTr.classList.add(`tr-${rNum}`);
// 1列目: slug (tb-td-X行-1列`)
const td1 = document.createElement('td');
td1.classList.add(`tb-td-${rNum}-1`);
td1.textContent = slug;
bTr.appendChild(td1);
// 2列目: PNG
const td2 = document.createElement('td');
td2.classList.add(`tb-td-${rNum}-2`);
const pngPath = `/wp-content/themes/wp-test/assets/images/png/awesome/${slug}.png`;
td2.innerHTML = `<img decoding="async" src="${pngPath}" alt="${slug}" width="24" height="24">`;
bTr.appendChild(td2);
// 3列目: SVG
const td3 = document.createElement('td');
td3.classList.add(`tb-td-${rNum}-3`);
td3.innerHTML = cleanSvg;
bTr.appendChild(td3);
tbody.appendChild(bTr);
});
}
initCsvTableTbody();
})();