<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <title>JSON -> HTML Table</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta
      name="description"
      content="JSON 데이터를 HTML 테이블로 쉽게 변환할 수 있는 페이지입니다."
    />
    <meta
      name="keywords"
      content="JSON, HTML, Table, JSON to Table, 변환, 캐스캐이딩"
    />
    <meta name="robots" content="index, follow" />
    <meta property="og:title" content="JSON -> HTML Table" />
    <meta
      property="og:description"
      content="JSON 데이터를 HTML 테이블로 손쉽게 변환할 수 있는 웹 도구입니다."
    />

    <meta property="og:url" content="https://example.com/json-to-table" />
    <meta property="og:type" content="website" />
    <!-- 필요 시 사이트 대표 이미지(썸네일) 경로 -->
    <!-- <meta property="og:image" content="https://example.com/thumbnail.jpg" /> -->
    <!-- Canonical 링크 -->
    <link rel="canonical" href="https://example.com/json-to-table" />
    <!-- 메타 태그(SEO, 소셜 미디어 등) 끝 -->
    <link
      href="https://cdn.jsdelivr.net/npm/tailwindcss@^2.0/dist/tailwind.min.css"
      rel="stylesheet"
    />

    <style>
      #tableOutput table {
        width: 100%;
        border-collapse: collapse;
        margin-top: 10px;
      }
      .row-color-1 {
        background-color: #f8f9fa;
      }
      .row-color-2 {
        background-color: #ffffff;
      }
      td,
      th {
        padding: 8px;
        border: 1px solid #dee2e6;
      }
      .table-loading {
        text-align: center;
        color: #888;
        padding: 20px;
      }
      /* 결과 영역 테두리 */
      #tableOutput {
        border: 1px solid #ddd;
        padding: 10px;
        min-height: 150px;
        margin-top: 10px;
      }
    </style>
  </head>
  <body class="m-5 font-sans">
    <!-- JSON 입력을 위한 textarea -->
    JSON to Table
    <select id="languageSelect" class="p-2">
      <option value="en">English</option>
      <option value="ko">한국어</option>
      <option value="es">Español</option>
      <option value="zh">中文(简体)</option>
      <option value="ja">日本語</option>
      <option value="de">Deutsch</option>
      <option value="fr">Français</option>
    </select>
    <textarea
      id="jsonInput"
      class="w-full h-72 border border-gray-300 mb-2 p-2 font-mono box-border"
    ></textarea>
    <div class="flex flex-row gap-4">
      <button
        id="btnConvert"
        class="mr-2 py-2 px-3 cursor-pointer bg-blue-500 text-white font-bold hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-700 focus:ring-opacity-50 shadow-lg transition duration-150 ease-in-out"
      >
        변환하기 (JSON to Table)</button
      ><button
        id="btnReset"
        class="mr-2 py-2 px-3 cursor-pointer bg-gray-500 text-white font-bold hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-700 focus:ring-opacity-50 shadow-lg transition duration-150 ease-in-out"
      >
        초기화하기
      </button>
      <button
        id="btnSample"
        class="mr-2 py-2 px-3 cursor-pointer bg-blue-500 text-white font-bold hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-700 focus:ring-opacity-50 shadow-lg transition duration-150 ease-in-out"
      >
        Load Sample JSON
      </button>
      <button
        id="btnCopyTable"
        class="mr-2 py-2 px-3 cursor-pointer bg-blue-500 text-white font-bold hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-700 focus:ring-opacity-50 shadow-lg transition duration-150 ease-in-out"
      >
        표 형식으로 복사하기
      </button>
      <button
        id="btnCopyTSV"
        class="mr-2 py-2 px-3 cursor-pointer bg-blue-500 text-white font-bold hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-700 focus:ring-opacity-50 shadow-lg transition duration-150 ease-in-out"
      >
        TSV로 복사하기
      </button>
      <button
        id="btnCopyCSV"
        class="mr-2 py-2 px-3 cursor-pointer bg-blue-500 text-white font-bold hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-700 focus:ring-opacity-50 shadow-lg transition duration-150 ease-in-out"
      >
        CSV로 복사하기
      </button>
      <button
        id="btnCopyHTML"
        class="mr-2 py-2 px-3 cursor-pointer bg-blue-500 text-white font-bold hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-700 focus:ring-opacity-50 shadow-lg transition duration-150 ease-in-out"
      >
        HTML 코드 복사하기
      </button>
    </div>
    <div
      id="tableOutput"
      class="border border-gray-300 p-2 mt-2 min-h-[150px]"
    ></div>

    <script>
      const languagePack = {
        en: {
          convertButton: "Convert (JSON to Table)",
          resetButton: "Reset",
          loadSampleJson: "Load Sample JSON",
          copyTable: "Copy as Table",
          copyTSV: "Copy as TSV",
          copyCSV: "Copy as CSV",
          copyHTML: "Copy HTML Code",
          invalidJson: "Invalid JSON format. Please check your input.",
          converting: "Converting to Table...",
          noTableToCopy: "No table to copy.",
          copiedToClipboard: "Copied to clipboard.",
        },
        ko: {
          convertButton: "변환하기 (JSON to Table)",
          resetButton: "초기화하기",
          loadSampleJson: "샘플 JSON 불러오기",
          copyTable: "표 형식으로 복사하기",
          copyTSV: "TSV로 복사하기",
          copyCSV: "CSV로 복사하기",
          copyHTML: "HTML 코드 복사하기",
          invalidJson: "잘못된 JSON 형식입니다. 입력을 확인해주세요.",
          converting: "표로 변환 중...",
          noTableToCopy: "복사할 테이블이 없습니다.",
          copiedToClipboard: "클립보드에 복사되었습니다.",
        },
        es: {
          convertButton: "Convertir (JSON a Tabla)",
          resetButton: "Reiniciar",
          loadSampleJson: "Cargar JSON de muestra",
          copyTable: "Copiar como Tabla",
          copyTSV: "Copiar como TSV",
          copyCSV: "Copiar como CSV",
          copyHTML: "Copiar código HTML",
          invalidJson:
            "Formato de JSON inválido. Por favor, verifica tu entrada.",
          converting: "Convirtiendo a tabla...",
          noTableToCopy: "No hay tabla para copiar.",
          copiedToClipboard: "Copiado al portapapeles.",
        },
        ja: {
          convertButton: "変換 (JSONからテーブルへ)",
          resetButton: "リセット",
          loadSampleJson: "サンプル JSON を読み込む",
          copyTable: "テーブル形式でコピー",
          copyTSV: "TSV形式でコピー",
          copyCSV: "CSV形式でコピー",
          copyHTML: "HTMLコードをコピー",
          invalidJson: "無効なJSON形式です。入力を確認してください。",
          converting: "テーブルに変換中...",
          noTableToCopy: "コピーするテーブルがありません。",
          copiedToClipboard: "クリップボードにコピーされました。",
        },
        zh: {
          convertButton: "转换 (JSON到表格)",
          resetButton: "重置",
          loadSampleJson: "加载样本 JSON",
          copyTable: "复制表格",
          copyTSV: "复制为TSV",
          copyCSV: "复制为CSV",
          copyHTML: "复制HTML代码",
          invalidJson: "JSON格式无效。请检查你的输入。",
          converting: "转换成表格中...",
          noTableToCopy: "没有表格可复制。",
          copiedToClipboard: "已复制到剪贴板。",
        },
        fr: {
          convertButton: "Convertir (JSON en Tableau)",
          resetButton: "Réinitialiser",
          loadSampleJson: "Charger un JSON exemple",
          copyTable: "Copier comme Tableau",
          copyTSV: "Copier comme TSV",
          copyCSV: "Copier comme CSV",
          copyHTML: "Copier le code HTML",
          invalidJson: "Format JSON invalide. Veuillez vérifier votre saisie.",
          converting: "Conversion en tableau...",
          noTableToCopy: "Aucun tableau à copier.",
          copiedToClipboard: "Copié dans le presse-papiers.",
        },
        de: {
          convertButton: "Umwandeln (JSON zu Tabelle)",
          resetButton: "Zurücksetzen",
          loadSampleJson: "Beispiel-JSON laden",
          copyTable: "Als Tabelle kopieren",
          copyTSV: "Als TSV kopieren",
          copyCSV: "Als CSV kopieren",
          copyHTML: "HTML-Code kopieren",
          invalidJson:
            "Ungültiges JSON-Format. Bitte überprüfen Sie Ihre Eingabe.",
          converting: "Konvertierung in Tabelle...",
          noTableToCopy: "Keine Tabelle zum Kopieren.",
          copiedToClipboard: "In die Zwischenablage kopiert.",
        },
      };

      function updateLanguage(language) {
        document.getElementById("btnConvert").textContent =
          languagePack[language].convertButton;
        document.getElementById("btnReset").textContent =
          languagePack[language].resetButton;
        document.getElementById("btnSample").textContent =
          languagePack[language].loadSampleJson;
        document.getElementById("btnCopyTable").textContent =
          languagePack[language].copyTable;
        document.getElementById("btnCopyTSV").textContent =
          languagePack[language].copyTSV;
        document.getElementById("btnCopyCSV").textContent =
          languagePack[language].copyCSV;
        document.getElementById("btnCopyHTML").textContent =
          languagePack[language].copyHTML;
      }

      // 페이지 로드 시 초기 설정
      window.addEventListener("DOMContentLoaded", function () {
        const supportedLanguages = ["en", "ko", "es", "ja", "zh", "fr", "de"]; // 지원하는 언어 목록 업데이트
        const defaultLanguage = "en"; // 기본 언어 설정

        // 브라우저 언어 가져오기
        let browserLanguage = navigator.language.slice(0, 2);

        // 지원하는 언어인지 확인
        if (!supportedLanguages.includes(browserLanguage)) {
          browserLanguage = defaultLanguage;
        }

        document
          .getElementById("languageSelect")
          .addEventListener("change", function () {
            updateLanguage(this.value);
          });

        // 페이지 언어 업데이트 함수 호출
        updateLanguage(browserLanguage);

        // 언어 선택 드롭다운에 브라우저 언어 설정
        document.getElementById("languageSelect").value = browserLanguage;

        // 페이지 로드시 샘플 JSON을 textarea에 로드
        document.getElementById("jsonInput").value = JSON.stringify(
          sampleJson,
          null,
          2
        );

        // 변환 버튼 클릭 이벤트 등록
        document
          .getElementById("btnConvert")
          .addEventListener("click", convertJsonToTable);

        // 샘플 JSON 버튼 클릭 이벤트 등록
        document
          .getElementById("btnSample")
          .addEventListener("click", function () {
            document.getElementById("jsonInput").value = JSON.stringify(
              sampleJson,
              null,
              2
            );
          });

        document
          .getElementById("btnCopyTable")
          .addEventListener("click", function () {
            const table = document
              .getElementById("tableOutput")
              .querySelector("table");
            if (!table) {
              alert("복사할 테이블이 없습니다.");
              return;
            }
            const selection = window.getSelection();
            const range = document.createRange();
            range.selectNodeContents(table);
            selection.removeAllRanges();
            selection.addRange(range);
            document.execCommand("copy");
            selection.removeAllRanges();
            alert("클립보드에 복사되었습니다.");
          });

        document
          .getElementById("btnCopyTSV")
          .addEventListener("click", function () {
            const table = document
              .getElementById("tableOutput")
              .querySelector("table");
            if (!table) {
              alert("복사할 테이블이 없습니다.");
              return;
            }
            let tsv = "";
            const rows = table.querySelectorAll("tr");
            rows.forEach((row) => {
              const cells = row.querySelectorAll("th, td");
              let rowTsv = [];
              cells.forEach((cell) => {
                rowTsv.push(cell.innerText);
              });
              tsv += rowTsv.join("\t") + "\n";
            });

            const el = document.createElement("textarea");
            el.value = tsv;
            document.body.appendChild(el);
            el.select();
            document.execCommand("copy");
            document.body.removeChild(el);
            alert("클립보드에 TSV 형식으로 복사되었습니다.");
          });

        document
          .getElementById("btnCopyCSV")
          .addEventListener("click", function () {
            const table = document
              .getElementById("tableOutput")
              .querySelector("table");
            if (!table) {
              alert("복사할 테이블이 없습니다.");
              return;
            }
            let csv = "";
            const rows = table.querySelectorAll("tr");
            rows.forEach((row) => {
              let rowData = [];
              const cells = row.querySelectorAll("th, td");
              cells.forEach((cell) => {
                const text = cell.innerText.replace(/"/g, '""'); // 쌍따옴표 이스케이프
                rowData.push(`"${text}"`);
              });
              csv += rowData.join(",") + "\r\n";
            });

            const el = document.createElement("textarea");
            el.value = csv;
            document.body.appendChild(el);
            el.select();
            document.execCommand("copy");
            document.body.removeChild(el);
            alert("클립보드에 CSV 형식으로 복사되었습니다.");
          });

        document
          .getElementById("btnCopyHTML")
          .addEventListener("click", function () {
            const table = document
              .getElementById("tableOutput")
              .querySelector("table");
            if (!table) {
              alert("복사할 테이블이 없습니다.");
              return;
            }
            const el = document.createElement("textarea");
            el.value = table.outerHTML; // table 태그의 HTML 코드를 복사
            document.body.appendChild(el);
            el.select();
            document.execCommand("copy");
            document.body.removeChild(el);
            alert("클립보드에 HTML 코드가 복사되었습니다.");
          });
        document
          .getElementById("btnReset")
          .addEventListener("click", function () {
            document.getElementById("jsonInput").value = ""; // JSON 입력 영역을 비움
            document.getElementById("tableOutput").innerHTML = ""; // 테이블 출력 영역을 비움
          });
      });

      // JSON 문자열을 파싱하여 HTML 테이블로 변환하는 함수
      function convertJsonToTable() {
        const outputDiv = document.getElementById("tableOutput");
        const rawText = document.getElementById("jsonInput").value.trim();

        // 입력값이 없으면 결과 영역 비우기
        if (!rawText) {
          outputDiv.innerHTML = "";
          return;
        }

        // 변환 중임을 표시
        outputDiv.innerHTML =
          '<div class="table-loading"><div>Converting to Table...</div></div>';

        let jsonData;
        try {
          jsonData = JSON.parse(rawText);
        } catch (e) {
          outputDiv.innerHTML =
            '<div style="color: red;">Invalid JSON format. Please check your input.<br/>';
          return;
        }

        // JSON 데이터에 따라 테이블 또는 내용을 생성
        const tableElement = generateTableFromJson(jsonData);

        // 결과 영역에 출력 (문자열이면 innerHTML, DOM 요소이면 appendChild)
        if (typeof tableElement === "string") {
          outputDiv.innerHTML = tableElement;
        } else {
          outputDiv.innerHTML = "";
          outputDiv.appendChild(tableElement);
        }
      }

      // JSON 데이터 유형에 따라 적절한 테이블(또는 내용) 생성
      function generateTableFromJson(data) {
        if (Array.isArray(data)) {
          return arrayToTable(data);
        } else if (typeof data === "object" && data !== null) {
          return objectToTable(data);
        } else {
          // 숫자, 문자열 등 원시값은 그대로 표시
          return `<p>${data}</p>`;
        }
      }

      /**
       * 배열을 HTML 테이블로 변환하는 함수
       *
       * - 배열의 모든 요소가 객체(단일 구조)인 경우:
       *   각 객체의 모든 키(Union)를 헤더로 사용하여 행을 생성합니다.
       * - 그렇지 않은 경우:
       *   각 요소를 1행 1열의 셀로 표시합니다.
       */
      function arrayToTable(arr) {
        // 빈 배열이면 빈 테이블 반환
        if (arr.length === 0) {
          return document.createElement("table");
        }

        // 모든 요소가 '단일 객체'인지 검사 (배열이 아닌 일반 객체)
        const allObjects = arr.every(
          (item) =>
            typeof item === "object" && item !== null && !Array.isArray(item)
        );

        if (allObjects) {
          // 모든 객체의 키의 합집합(Union) 구하기
          let keySet = new Set();
          arr.forEach((item) => {
            Object.keys(item).forEach((key) => keySet.add(key));
          });
          const keys = Array.from(keySet);

          // 테이블 생성
          const table = document.createElement("table");
          table.className = "table table-bordered";

          // 테이블 헤더 생성
          const thead = document.createElement("thead");
          const headerRow = document.createElement("tr");
          keys.forEach((key) => {
            const th = document.createElement("th");
            th.textContent = key;
            headerRow.appendChild(th);
          });
          thead.appendChild(headerRow);
          table.appendChild(thead);

          // 테이블 바디 생성
          const tbody = document.createElement("tbody");
          arr.forEach((obj, idx) => {
            const row = document.createElement("tr");
            row.className = idx % 2 === 0 ? "row-color-1" : "row-color-2";
            keys.forEach((key) => {
              const td = document.createElement("td");
              // 객체에 해당 key가 있으면 값 출력, 없으면 빈 셀 처리
              let value = obj.hasOwnProperty(key) ? obj[key] : "";
              if (typeof value === "object" && value !== null) {
                td.appendChild(objectOrArrayToTable(value));
              } else {
                td.textContent = value;
              }
              row.appendChild(td);
            });
            tbody.appendChild(row);
          });
          table.appendChild(tbody);
          return table;
        } else {
          // 객체가 아닌 요소나 혼합된 경우 -> 각 요소별로 1행 1열 셀로 처리
          const table = document.createElement("table");
          table.className = "table table-bordered";
          arr.forEach((item, idx) => {
            const row = table.insertRow();
            row.className = idx % 2 === 0 ? "row-color-1" : "row-color-2";
            const cell = row.insertCell();
            if (typeof item === "object" && item !== null) {
              cell.appendChild(objectOrArrayToTable(item));
            } else {
              cell.textContent = item;
            }
          });
          return table;
        }
      }

      /**
       * 객체를 HTML 테이블로 변환하는 함수
       *   각 key/value 쌍을 행으로 만들어 표시합니다.
       */
      function objectToTable(obj) {
        const table = document.createElement("table");
        table.className = "table table-bordered";

        let rowColorSwitch = true;
        for (const [key, value] of Object.entries(obj)) {
          const row = table.insertRow();
          row.className = rowColorSwitch ? "row-color-1" : "row-color-2";
          rowColorSwitch = !rowColorSwitch;

          // key 셀 (굵은 글씨)
          const keyCell = row.insertCell();
          keyCell.textContent = key;
          keyCell.style.fontWeight = "bold";

          // value 셀 (재귀 호출 가능)
          const valueCell = row.insertCell();
          if (typeof value === "object" && value !== null) {
            valueCell.appendChild(objectOrArrayToTable(value));
          } else {
            valueCell.textContent = value;
          }
        }
        return table;
      }

      /**
       * 데이터가 배열인지 객체인지 판단하여 각각에 맞는 테이블 생성 후 반환합니다.
       */
      function objectOrArrayToTable(data) {
        if (Array.isArray(data)) {
          return arrayToTable(data);
        } else {
          return objectToTable(data);
        }
      }
    </script>
  </body>
</html>
