aboutsummaryrefslogtreecommitdiffhomepage
path: root/scripts/park-editor.mjs
diff options
context:
space:
mode:
authorYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-27 15:21:44 +0900
committerYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-27 15:21:44 +0900
commitcb24e2fd1fab1924084f2765bb870f01570544a2 (patch)
tree0f00433e7203ef3c231be4f56ac8bc3faf9afa09 /scripts/park-editor.mjs
parent32d73749135dbe217fa94a96fe19bc966b3b8de6 (diff)
park-editor: 文字化けを保存時に警告し、場所を表示
保存を中止せず、該当セルを赤くハイライトして、保存前に公園名・項目・値の一覧を確認できるようにした。サーバー側も保存を継続し、警告を返す。
Diffstat (limited to 'scripts/park-editor.mjs')
-rw-r--r--scripts/park-editor.mjs45
1 files changed, 41 insertions, 4 deletions
diff --git a/scripts/park-editor.mjs b/scripts/park-editor.mjs
index d0c08da..e3bae45 100644
--- a/scripts/park-editor.mjs
+++ b/scripts/park-editor.mjs
@@ -51,6 +51,38 @@ function datasetFromUrl(url) {
return DATASETS[match[1]] ? { id: match[1], dataset: DATASETS[match[1]] } : null;
}
+// 文字化け(U+FFFD)の場所を「レコード名 / 項目名」で返す。
+// 保存は止めず、どこを直せばよいか分かるようにするための情報。
+const NAME_KEYS = ["公園名", "name", "名称", "our_name"];
+function recordLabel(obj) {
+ const sources = [];
+ if (obj && obj.properties) sources.push(obj.properties);
+ sources.push(obj);
+ for (const source of sources) {
+ for (const key of NAME_KEYS) {
+ const value = source && source[key];
+ if (typeof value === "string" && value) return value;
+ }
+ }
+ return "";
+}
+function findCorruptions(value, path, out) {
+ if (typeof value === "string") {
+ if (value.includes("\uFFFD")) out.push({ where: path, value });
+ } else if (Array.isArray(value)) {
+ value.forEach((item, index) => {
+ const label = recordLabel(item);
+ findCorruptions(item, label || `${path}[${index + 1}]`, out);
+ });
+ } else if (value && typeof value === "object") {
+ for (const [key, child] of Object.entries(value)) {
+ if (key.includes("\uFFFD")) out.push({ where: `${path} / ${key}(項目名)`, value: "" });
+ findCorruptions(child, path ? `${path} / ${key}` : key, out);
+ }
+ }
+ return out;
+}
+
const server = createServer(async (req, res) => {
try {
const url = (req.url || "").split("?")[0];
@@ -104,9 +136,14 @@ const server = createServer(async (req, res) => {
? `${JSON.stringify(data, null, 2)}\n`
: `${JSON.stringify(data)}\n`;
if (text.includes("\uFFFD")) {
- throw new Error(
- "文字化け(U+FFFD)が含まれているため保存を中止しました。入力内容を確認してください。",
- );
+ // 保存は中止せず、どこが文字化けしているかを返す(入力内容を失わないため)
+ console.warn("文字化け(U+FFFD)を含むデータを保存しました");
+ }
+ const warnings = findCorruptions(data, "", []).slice(0, 50);
+ if (warnings.length) {
+ for (const warning of warnings.slice(0, 10)) {
+ console.warn(` 文字化け: ${warning.where} = ${warning.value}`);
+ }
}
const tmp = `${target.dataset.path}.tmp`;
await writeFile(tmp, text, "utf-8");
@@ -118,7 +155,7 @@ const server = createServer(async (req, res) => {
res,
200,
"application/json; charset=utf-8",
- JSON.stringify({ ok: true, count }),
+ JSON.stringify({ ok: true, count, warnings }),
);
return;
}