aboutsummaryrefslogtreecommitdiffhomepage
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/park-editor.mjs98
1 files changed, 94 insertions, 4 deletions
diff --git a/scripts/park-editor.mjs b/scripts/park-editor.mjs
index 48b9f17..2ccd96d 100644
--- a/scripts/park-editor.mjs
+++ b/scripts/park-editor.mjs
@@ -83,6 +83,8 @@ function findCorruptions(value, path, out) {
return out;
}
+const FFFD = "\uFFFD";
+
function recordKey(obj) {
const src = obj && obj.properties ? obj.properties : obj;
if (!src) return null;
@@ -93,9 +95,92 @@ function recordKey(obj) {
return null;
}
+function canonicalKeysFrom(lists) {
+ const keys = new Set();
+ for (const list of lists) {
+ for (const rec of list || []) {
+ const src = rec && rec.properties ? rec.properties : rec;
+ if (!src) continue;
+ for (const key of Object.keys(src)) {
+ if (!key.includes(FFFD)) keys.add(key);
+ }
+ }
+ }
+ return [...keys];
+}
+
+// 「ベンチ・椅���」のような項目名の文字化けを、前後の文字から正しい項目名に戻す
+function normalizeRecordKeys(rec, canonicalKeys) {
+ const src = rec && rec.properties ? rec.properties : rec;
+ if (!src) return;
+ for (const bad of Object.keys(src)) {
+ if (!bad.includes(FFFD)) continue;
+ const prefix = bad.split(FFFD)[0];
+ const suffix = bad.split(FFFD).slice(-1)[0];
+ const candidates = canonicalKeys.filter(
+ (key) => key.startsWith(prefix) && key.endsWith(suffix),
+ );
+ if (candidates.length !== 1) continue;
+ const good = candidates[0];
+ const value = src[bad];
+ if (!(good in src)) {
+ src[good] = value;
+ delete src[bad];
+ } else if (!src[good] && value) {
+ src[good] = value;
+ delete src[bad];
+ } else if (src[good] === value) {
+ delete src[bad];
+ }
+ }
+}
+
+// 値の文字化けが「何文字かの欠落」だけなら、ディスク側の値で直す
+function repairValue(posted, current) {
+ if (typeof posted !== "string" || !posted.includes(FFFD)) return posted;
+ if (typeof current !== "string" || !current) return posted;
+ const parts = posted.split(/\uFFFD+/);
+ if (parts.length < 2) return posted;
+ const pattern =
+ "^" +
+ parts.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("(.{1,4}?)") +
+ "$";
+ return new RegExp(pattern, "s").test(current) ? current : posted;
+}
+
+function mergeInto(target, source) {
+ const dst = target.properties || target;
+ const src = source.properties || source;
+ for (const [field, value] of Object.entries(src)) {
+ if (!(field in dst) || (!dst[field] && value)) dst[field] = value;
+ }
+}
+
// ブラウザが古いデータを持っていても、後から足した項目やレコードを消さないようにする。
// 画面になくて編集できないキーは、ディスク側の値を残す。
-function mergeMissing(postedList, currentList) {
+function mergeMissing(postedList, currentList, canonicalKeys) {
+ for (const rec of postedList) normalizeRecordKeys(rec, canonicalKeys);
+
+ // 同じレコードが重複していたら1つにまとめる
+ const deduped = [];
+ const byKey = new Map();
+ for (const rec of postedList) {
+ const key = recordKey(rec);
+ if (!key) {
+ deduped.push(rec);
+ continue;
+ }
+ const existing = byKey.get(key);
+ if (existing) {
+ mergeInto(existing, rec);
+ continue;
+ }
+ byKey.set(key, rec);
+ deduped.push(rec);
+ }
+ postedList.length = 0;
+ for (const rec of deduped) postedList.push(rec);
+
const map = new Map();
for (const rec of currentList) {
const key = recordKey(rec);
@@ -111,7 +196,11 @@ function mergeMissing(postedList, currentList) {
const dst = rec.properties || rec;
const src = current.properties || current;
for (const [field, value] of Object.entries(src)) {
- if (!(field in dst)) dst[field] = value;
+ if (!(field in dst)) {
+ dst[field] = value;
+ } else if (typeof dst[field] === "string" && dst[field].includes(FFFD)) {
+ dst[field] = repairValue(dst[field], value);
+ }
}
}
for (const [key, rec] of map) {
@@ -170,10 +259,11 @@ const server = createServer(async (req, res) => {
// 古い画面からの保存で、新しい項目・レコードが消えないようにマージする
const current = JSON.parse(await readFile(target.dataset.path, "utf-8"));
+ const canonicalKeys = canonicalKeysFrom([current, data.features || data]);
if (target.id === "nearbyParks") {
- mergeMissing(data.features, current.features || []);
+ mergeMissing(data.features, current.features || [], canonicalKeys);
} else {
- mergeMissing(data, current);
+ mergeMissing(data, current, canonicalKeys);
}
const text = target.dataset.pretty