此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.greasyforks.org/scripts/548745/1656811/WME%20PLN%20Core%20-%20XML%20Handler.js
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
(我已經安裝了使用者樣式管理器,讓我安裝!)
// ==UserScript==
// @name WME PLN Core - XML Handler
// @namespace https://greasyforks.org/en/users/mincho77
// @version 9.0.0
// @description Módulo para importar y exportar datos en formato XML para WME Place Normalizer. No funciona por sí solo.
// @author mincho77
// @license MIT
// @grant none
// ==/UserScript==
function exportSharedDataToXml(type = "full") {
try {
const _excludedWords = (window.excludedWords instanceof Set) ? window.excludedWords : new Set(Array.isArray(window.excludedWords) ? window.excludedWords : []);
const _replacementWords = (window.replacementWords && typeof window.replacementWords === 'object') ? window.replacementWords : {};
const _swapWords = Array.isArray(window.swapWords) ? window.swapWords : [];
const _editorStats = (window.editorStats && typeof window.editorStats === 'object') ? window.editorStats : {};
const _excludedPlaces = (window.excludedPlaces instanceof Map) ? window.excludedPlaces : new Map();
const _processedPlaces = Array.isArray(window.processedPlaces) ? window.processedPlaces : [];
let xmlParts = [];
const rootTagName = "WME_PLN_Backup";
const fileName = "wme_pln_full_backup.xml";
if (_excludedWords.size === 0 && Object.keys(_replacementWords).length === 0 && _swapWords.length === 0 && Object.keys(_editorStats).length === 0 && _excludedPlaces.size === 0 && _processedPlaces.length === 0) {
alert("No hay datos para exportar.");
return;
}
if (_excludedWords.size > 0) {
xmlParts.push(" <words>");
Array.from(_excludedWords).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())).forEach(w => xmlParts.push(` <word>${xmlEscape(w)}</word>`));
xmlParts.push(" </words>");
}
if (Object.keys(_replacementWords).length > 0) {
xmlParts.push(" <replacements>");
Object.entries(_replacementWords).sort((a, b) => a[0].toLowerCase().localeCompare(b[0].toLowerCase())).forEach(([from, to]) => {
xmlParts.push(` <replacement from="${xmlEscape(from)}">${xmlEscape(to)}</replacement>`);
});
xmlParts.push(" </replacements>");
}
if (_swapWords.length > 0) {
xmlParts.push(" <swapWords>");
_swapWords.forEach(item => {
xmlParts.push(` <swap value="${xmlEscape(item.word || '')}" direction="${xmlEscape(item.direction || 'start')}"/>`);
});
xmlParts.push(" </swapWords>");
}
if (Object.keys(_editorStats).length > 0) {
xmlParts.push(" <statistics>");
Object.entries(_editorStats).forEach(([userId, data]) => {
xmlParts.push(` <editor id="${xmlEscape(userId)}" name="${xmlEscape(data?.userName || '')}" total_count="${data?.total_count || 0}" monthly_count="${data?.monthly_count || 0}" monthly_period="${xmlEscape(data?.monthly_period || '')}" weekly_count="${data?.weekly_count || 0}" weekly_period="${xmlEscape(data?.weekly_period || '')}" daily_count="${data?.daily_count || 0}" daily_period="${xmlEscape(data?.daily_period || '')}" last_update="${data?.last_update || 0}" />`);
});
xmlParts.push(" </statistics>");
}
if (_excludedPlaces.size > 0) {
xmlParts.push(" <excludedPlaces>");
Array.from(_excludedPlaces.entries()).sort((a,b) => String(a[0]).localeCompare(String(b[0]))).forEach(([id, name]) => {
xmlParts.push(` <place id="${xmlEscape(String(id))}" name="${xmlEscape(String(name || ''))}"/>`);
});
xmlParts.push(" </excludedPlaces>");
}
if (typeof exportProcessedPlacesSectionXML === 'function' && _processedPlaces.length > 0) {
xmlParts.push(exportProcessedPlacesSectionXML());
}
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>\n<${rootTagName}>\n${xmlParts.join("\n")}\n</${rootTagName}>`;
const blob = new Blob([xmlContent], { type: "application/xml;charset=utf-8" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (err) {
console.error('[WME PLN] exportSharedDataToXml error:', err);
alert('No fue posible exportar el XML. Revisa la consola para más detalles.');
}
}
function handleXmlFileDrop(file, type = "words") {
if (!file || !(file.type === "text/xml" || (file.name || '').endsWith(".xml"))) {
alert("Por favor, arrastra un archivo XML válido.");
return;
}
const reader = new FileReader();
reader.onload = function(evt) {
try {
let newExcludedAdded = 0, newReplacementsAdded = 0, replacementsOverwritten = 0, newSwapWordsAdded = 0, newPlacesAdded = 0;
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(evt.target.result, "application/xml");
const parserError = xmlDoc.querySelector("parsererror");
if (parserError) {
alert("Error al parsear el archivo XML: " + parserError.textContent);
return;
}
const rootTag = (xmlDoc.documentElement.tagName || '').toLowerCase();
if (type === "words") {
if (rootTag !== "excludedwords") {
alert("El XML no es válido para Palabras Especiales. Raíz esperada: <ExcludedWords>.");
return;
}
const wordsNode = xmlDoc.querySelector("excludedwords > words");
if (wordsNode) {
const items = Array.from(wordsNode.querySelectorAll("word")).map(n => (n.textContent || '').trim()).filter(Boolean);
if (!window.excludedWords || !(window.excludedWords instanceof Set)) window.excludedWords = new Set();
items.forEach(w => { if (!window.excludedWords.has(w)) { window.excludedWords.add(w); newExcludedAdded++; } });
}
const replNode = xmlDoc.querySelector("excludedwords > replacements");
if (replNode) {
if (!window.replacementWords || typeof window.replacementWords !== 'object') window.replacementWords = {};
Array.from(replNode.querySelectorAll("replacement")).forEach(n => {
const from = n.getAttribute("from") || "";
const to = (n.textContent || "").trim();
if (!from) return;
if (from in window.replacementWords) replacementsOverwritten++;
else newReplacementsAdded++;
window.replacementWords[from] = to;
});
}
const swapNode = xmlDoc.querySelector("excludedwords > swapwords");
if (swapNode) {
if (!Array.isArray(window.swapWords)) window.swapWords = [];
Array.from(swapNode.querySelectorAll("swap")).forEach(n => {
const word = n.getAttribute("value") || "";
const direction = n.getAttribute("direction") || "start";
if (word) {
window.swapWords.push({ word, direction });
newSwapWordsAdded++;
}
});
}
const statsNode = xmlDoc.querySelector("excludedwords > statistics");
if (statsNode && window.editorStats && typeof window.editorStats === 'object') {
const editorNode = statsNode.querySelector("editor");
if (editorNode) {
const editorId = editorNode.getAttribute("id");
if (editorId && window.editorStats[editorId]) {
window.editorStats[editorId] = {
userName: editorNode.getAttribute("name") || window.editorStats[editorId].userName,
total_count: parseInt(editorNode.getAttribute("total_count"), 10) || 0,
monthly_count: parseInt(editorNode.getAttribute("monthly_count"), 10) || 0,
monthly_period: editorNode.getAttribute("monthly_period") || '',
weekly_count: parseInt(editorNode.getAttribute("weekly_count"), 10) || 0,
weekly_period: editorNode.getAttribute("weekly_period") || '',
daily_count: parseInt(editorNode.getAttribute("daily_count"), 10) || 0,
daily_period: editorNode.getAttribute("daily_period") || '',
last_update: parseInt(editorNode.getAttribute("last_update"), 10) || 0
};
}
}
}
try { saveExcludedWordsToLocalStorage?.(); } catch (_) { }
try { saveReplacementWordsToStorage?.(); } catch (_) { }
try { saveSwapWordsToStorage?.(); } catch (_) { }
try { saveEditorStats?.(); } catch (_) { }
try { renderExcludedWordsList?.(document.getElementById("excludedWordsList")); } catch (_) { }
try { renderReplacementsList?.(document.getElementById("replacementsListElementID")); } catch (_) { }
try { updateStatsDisplay?.(); } catch (_) { }
alert(`Importación completada.\n- Palabras nuevas: ${newExcludedAdded}\n- Reemplazos nuevos: ${newReplacementsAdded}\n- Reemplazos sobrescritos: ${replacementsOverwritten}\n- SwapWords importadas: ${newSwapWordsAdded}`);
} else if (type === "places") {
if (rootTag !== "excludedplaces") {
alert("El XML no es válido para Lugares Excluidos. Raíz esperada: <ExcludedPlaces>.");
return;
}
if (!(window.excludedPlaces instanceof Map)) window.excludedPlaces = new Map();
const nodes = xmlDoc.querySelectorAll("excludedplaces > placeids > placeid");
nodes.forEach(n => {
const id = n.getAttribute("id") || "";
const name = n.getAttribute("name") || "";
if (id && !window.excludedPlaces.has(id)) {
window.excludedPlaces.set(id, name);
newPlacesAdded++;
}
});
try { saveExcludedPlacesToLocalStorage?.(); } catch (_) { }
try { renderExcludedPlacesList?.(document.getElementById("excludedPlacesListUL")); } catch (_) { }
alert(`Importación completada.\n- Lugares excluidos nuevos: ${newPlacesAdded}`);
}
} catch (err) {
console.error("[WME PLN] Error procesando el XML:", err);
alert("Ocurrió un error procesando el archivo XML.");
}
};
reader.readAsText(file);
}
function exportProcessedPlacesSectionXML() {
try {
if (!Array.isArray(window.processedPlaces) || window.processedPlaces.length === 0) {
return '';
}
let xml = ' <processedPlaces>\n';
window.processedPlaces.forEach(p => {
xml += ` <place id="${xmlEscape(p.placeId || '')}"
name="${xmlEscape(p.name || '')}"
city="${xmlEscape(p.city || '')}"
department="${xmlEscape(p.department || '')}"
modifiedAt="${xmlEscape(p.modifiedAt || '')}"
editorId="${xmlEscape(p.editorId || '')}"
editorName="${xmlEscape(p.editorName || '')}" />\n`;
});
xml += ' </processedPlaces>';
return xml;
} catch (e) {
console.error('[WME PLN] Error generando XML de lugares procesados:', e);
return '';
}
}
function importProcessedPlacesFromXML(xmlString) {
try {
if (!xmlString || typeof xmlString !== 'string') return;
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlString, "application/xml");
const placeNodes = xmlDoc.querySelectorAll("processedPlaces > place");
if (placeNodes.length === 0) return;
let importedCount = 0;
placeNodes.forEach(node => {
const entry = {
placeId: node.getAttribute('id') || '',
name: node.getAttribute('name') || '',
city: node.getAttribute('city') || 'N/A',
department: node.getAttribute('department') || 'N/A',
modifiedAt: node.getAttribute('modifiedAt') || new Date().toISOString(),
editorId: Number(node.getAttribute('editorId')) || 0,
editorName: node.getAttribute('editorName') || 'Importado'
};
if (entry.placeId && entry.editorId) {
addProcessedPlace(entry);
importedCount++;
}
});
if (importedCount > 0) {
plnToast(`✅ Importados ${importedCount} registros de Lugares Procesados.`, 2500);
}
} catch (e) {
console.error('[WME PLN] Error importando XML de lugares procesados:', e);
}
}
function plnAttachProcessedPlacesToXML(xmlString) {
try {
let s = String(xmlString || '');
if (!s.trim()) return s;
if (!s.match(/<\w+.*?>/)) s = `<data>${s}</data>`;
if (typeof exportProcessedPlacesSectionXML === 'function' && !/<processedPlaces[\s>]/i.test(s)) {
const processedSection = '\n' + exportProcessedPlacesSectionXML() + '\n';
if (s.includes('</ExcludedWords>')) {
s = s.replace(/\n?<\/ExcludedWords>\s*$/i, processedSection + '</ExcludedWords>');
} else if (s.includes('</data>')) {
s = s.replace(/\n?<\/data>\s*$/i, processedSection + '</data>');
} else {
s += processedSection;
}
}
return s;
} catch (_) {
return xmlString;
}
}