您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Módulo para importar y exportar datos en formato XML para WME Place Normalizer. No funciona por sí solo.
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyforks.org/scripts/548745/1657856/WME%20PLN%20Core%20-%20XML%20Handler.js
// ==UserScript== // @name WME PLN Core - XML Handler // @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== // Pure core: build XML string based on provided payload (no DOM/Blob/UI) function buildSharedDataXML(type = "words", payload) { try { let xmlParts = []; let rootTagName = "SharedData"; let fileName = "wme_normalizer_data_export.xml"; // Normalize payload pieces to plain JS structures const excludedWordsArr = Array.isArray(payload?.excludedWords) ? payload.excludedWords : Array.from(payload?.excludedWords || []); const replacementWordsOb = payload?.replacementWords || {}; const swapWordsArr = Array.isArray(payload?.swapWords) ? payload.swapWords : []; const editorStatsObj = payload?.editorStats || {}; const excludedPlacesEntries = Array.isArray(payload?.excludedPlaces) ? payload.excludedPlaces : (payload?.excludedPlaces instanceof Map ? Array.from(payload.excludedPlaces.entries()) : []); if (type === "words") { rootTagName = "ExcludedWords"; fileName = "wme_excluded_words_export.xml"; if (excludedWordsArr.length === 0 && Object.keys(replacementWordsOb).length === 0 && swapWordsArr.length === 0 && Object.keys(editorStatsObj).length === 0) { return { xmlContent: "", fileName, empty: true, reason: "No hay datos de palabras/reemplazos/swap/estadísticas para exportar." }; } // Palabras especiales if (excludedWordsArr.length > 0) { xmlParts.push(" <words>"); excludedWordsArr .slice() .sort((a,b)=>String(a).toLowerCase().localeCompare(String(b).toLowerCase())) .forEach(w => xmlParts.push(` <word>${xmlEscape(w)}</word>`)); xmlParts.push(" </words>"); } // Reemplazos if (Object.keys(replacementWordsOb).length > 0) { xmlParts.push(" <replacements>"); Object.entries(replacementWordsOb) .sort((a,b)=>String(a[0]).toLowerCase().localeCompare(String(b[0]).toLowerCase())) .forEach(([from,to])=>{ xmlParts.push(` <replacement from="${xmlEscape(from)}">${xmlEscape(to)}</replacement>`); }); xmlParts.push(" </replacements>"); } // SwapWords if (swapWordsArr.length > 0) { xmlParts.push(" <swapWords>"); swapWordsArr.forEach(item=>{ if (item && typeof item === "object") { xmlParts.push(` <swap value="${xmlEscape(item.word)}" direction="${item.direction||"start"}"/>`); } else { xmlParts.push(` <swap value="${xmlEscape(item)}" direction="start"/>`); } }); xmlParts.push(" </swapWords>"); } // Estadísticas if (Object.keys(editorStatsObj).length > 0) { xmlParts.push(" <statistics>"); Object.entries(editorStatsObj).forEach(([userId, data])=>{ xmlParts.push(` <editor id="${userId}" ` + `name="${xmlEscape(data.userName||"")}" ` + `total_count="${data.total_count||0}" ` + `monthly_count="${data.monthly_count||0}" ` + `monthly_period="${data.monthly_period||""}" ` + `weekly_count="${data.weekly_count||0}" ` + `weekly_period="${data.weekly_period||""}" ` + `daily_count="${data.daily_count||0}" ` + `daily_period="${data.daily_period||""}" ` + `last_update="${data.last_update||0}" />`); }); xmlParts.push(" </statistics>"); } } else if (type === "places") { rootTagName = "ExcludedPlaces"; fileName = "wme_excluded_places_export.xml"; if (excludedPlacesEntries.length === 0) { return { xmlContent: "", fileName, empty: true, reason: "No hay lugares excluidos para exportar." }; } xmlParts.push(" <placeIds>"); excludedPlacesEntries .slice() .sort((a,b)=>String(a[0]).toLowerCase().localeCompare(String(b[0]).toLowerCase())) .forEach(([id,name])=>{ xmlParts.push(` <placeId id="${xmlEscape(id)}" name="${xmlEscape(name)}"></placeId>`); }); xmlParts.push(" </placeIds>"); } else { return { xmlContent: "", fileName, empty: true, reason: "Tipo de exportación XML desconocido." }; } const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>\n<${rootTagName}>\n${xmlParts.join("\n")}\n</${rootTagName}>`; return { xmlContent, fileName, empty: false }; } catch (e) { plnLog('error', '[core_xml] buildSharedDataXML failed:', e); return { xmlContent: "", fileName: "error.xml", empty: true, reason: e?.message||'unknown' }; } }//buildSharedDataXML // Pure core: parse XML text and return data without side effects (no DOM/UI writes here) function parseSharedDataXML(type = "words", xmlText) { try { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(String(xmlText||''), "application/xml"); const parserError = xmlDoc.querySelector("parsererror"); if (parserError) { return { success:false, error: 'Error al parsear el XML', details: parserError.textContent }; } const rootTag = xmlDoc.documentElement.tagName.toLowerCase(); const result = { success: true, type, newWords: 0, newReplacements: 0, overwrittenReplacements: 0, newPlaces: 0, // Parsed structures (caller decides how to merge) excludedWords: new Set(), replacementWords: {}, swapWords: [], editorStats: {}, excludedPlaces: new Map() }; if (type === "words") { if (rootTag !== "excludedwords") { return { success:false, error:'Raíz incorrecta: se esperaba <ExcludedWords>.' }; } // Words const words = xmlDoc.getElementsByTagName("word"); for (let i=0; i<words.length; i++) { const val = (words[i].textContent||'').trim(); if (val) { if (!result.excludedWords.has(val)) result.newWords++; result.excludedWords.add(val); } } // Reemplazos const replacementNodes = xmlDoc.getElementsByTagName("replacement"); for (let i=0; i<replacementNodes.length; i++) { const from = (replacementNodes[i].getAttribute("from")||'').trim(); const to = (replacementNodes[i].textContent||'').trim(); if (from && to) { if (from in result.replacementWords) { if (result.replacementWords[from] !== to) result.overwrittenReplacements++; } else { result.newReplacements++; } result.replacementWords[from] = to; } } } else if (type === "places") { if (rootTag !== "excludedplaces") { return { success:false, error:'Raíz incorrecta: se esperaba <ExcludedPlaces>.' }; } const placesNodes = xmlDoc.getElementsByTagName("placeId"); for (let i=0; i<placesNodes.length; i++) { const placeId = (placesNodes[i].getAttribute("id")||'').trim(); const placeName = (placesNodes[i].textContent||'').trim(); if (placeId) { if (!result.excludedPlaces.has(placeId)) result.newPlaces++; result.excludedPlaces.set(placeId, placeName); } } } else { return { success:false, error:'Tipo de importación XML desconocido.' }; } // SwapWords (optional section) const swapWordsNode = xmlDoc.querySelector("swapWords"); if (swapWordsNode) { swapWordsNode.querySelectorAll("swap").forEach(swapNode => { const word = swapNode.getAttribute("value"); const direction = swapNode.getAttribute("direction") || "start"; if (word && !result.swapWords.some(item => (typeof item === 'object' ? item.word : item) === word)) { result.swapWords.push({ word, direction }); } }); } // Statistics (optional) const statsNode = xmlDoc.querySelector("statistics"); if (statsNode) { const editorNode = statsNode.querySelector("editor"); if (editorNode && editorNode.hasAttribute("total_count")) { const uid = editorNode.getAttribute("id"); if (uid) { result.editorStats[uid] = { userName: editorNode.getAttribute("name") || '', 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 }; } } } return result; } catch (err) { return { success:false, error:'Excepción parseSharedDataXML', details: String(err) }; } }//parseSharedDataXML