User:SuperHamster/rsp-to-json.js
Appearance
From Wikipedia, the free encyclopedia
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump.
This code will be executed when previewing this page.
This code will be executed when previewing this page.
This user script seems to have a documentation page at User:SuperHamster/rsp-to-json.
/** * Parses the perennial sources table and generates a JSON structure for each source. * @param {string} tableClass - The class name of the table to parse. * @returns {Array<Object>} Array of source objects. */ functionparsePerennialSourcesTable(tableClass){ consttable=document.querySelector(`.${tableClass}`); constsources=[]; if(table){ constrows=table.querySelectorAll("tbody > tr"); rows.forEach((row,rowIndex)=>{ constcells=row.querySelectorAll("td"); if(cells.length<6){ return; } constsourceNameCell=cells[0]; conststatusCell=cells[1]; constdiscussionCell=cells[2]; constlastCell=cells[3]; constsummaryCell=cells[4]; constdomainsCell=cells[5]; constsource={ name:findSourceName(sourceNameCell), link:findSourceLink(sourceNameCell), shortcuts:findShortcuts(sourceNameCell), status:findStatus(statusCell), blacklisted:isBlacklisted(statusCell), discussions:parseDiscussions(discussionCell), lastDiscussed:lastCell.textContent.trim(), summary:summaryCell.textContent.trim(), summary_wikitext:convertHtmlToWikiMarkup(summaryCell), domains:findDomains(domainsCell), }; sources.push(source); }); }else{ console.error(`[RSP-to-JSON] Table with class ${tableClass} not found`); } returnsources; } /** * Checks if an element or any of its ancestors has a given class. * @param {Element} element - The DOM element to check. * @param {string} className - The class name to look for. * @returns {boolean} True if the class is found, false otherwise. */ functionhasAncestorWithClass(element,className){ while(element){ if(element.classList&&element.classList.contains(className))returntrue; element=element.parentElement; } returnfalse; } /** * Extracts the source name from a table cell. * @param {Element} cell - The table cell element. * @returns {string} The extracted source name. */ functionfindSourceName(cell){ functionextractTextFromNode(node){ if(node.nodeType===Node.TEXT_NODE){ returnnode.textContent.trim(); }elseif(node.nodeType===Node.ELEMENT_NODE&&(node.tagName==="A"||node.tagName==="I")){ returnArray.from(node.childNodes).map(extractTextFromNode).join(" ").trim(); } return""; } // Traverse child nodes to locate the source name and combine all text letsourceName=Array.from(cell.childNodes) .map(extractTextFromNode) .filter(text=>text) .join(" ") .trim(); returnsourceName||""; } /** * Finds the main source link in a table cell, ignoring shortcut links. * @param {Element} cell - The table cell element. * @returns {string} The href of the main source link, or an empty string if not found. */ functionfindSourceLink(cell){ constlinkElement=Array.from(cell.querySelectorAll("a")).find(link=>!hasAncestorWithClass(link,"wp-rsp-sc")); returnlinkElement?linkElement.href:""; } /** * Finds all shortcut links in a table cell. * @param {Element} cell - The table cell element. * @returns {Array<string>} Array of shortcut strings. */ functionfindShortcuts(cell){ constshortcuts=Array.from(cell.querySelectorAll(".wp-rsp-sc a")).map(anchor=>anchor.textContent.trim()); returnshortcuts; } /** * Determines the status of a source from a table cell. * @param {Element} cell - The table cell element. * @returns {string} The status string (e.g., 'deprecated', 'generally reliable', etc.). */ functionfindStatus(cell){ anchors=cell.querySelectorAll('a'); statuses=[]; anchors.forEach(anchor=>{ statuses.push(anchor.title.toLowerCase()); }); if(statuses.includes("deprecated"))return"deprecated"; if(statuses.includes("generally reliable"))return"generally reliable"; if(statuses.includes("generally unreliable"))return"generally unreliable"; if(statuses.includes("no consensus"))return"no consensus"; if(statuses.includes("blacklisted"))return"blacklisted"; return"unknown"; } /** * Checks if a source is blacklisted based on the cell content. * @param {Element} cell - The table cell element. * @returns {boolean} True if blacklisted, false otherwise. */ functionisBlacklisted(cell){ constblacklisted=!!cell.querySelector("a[title='Blacklisted']"); returnblacklisted; } /** * Parses the discussions cell to extract discussion links and metadata. * @param {Element} cell - The table cell element. * @returns {Array<Object>} Array of discussion objects. */ functionparseDiscussions(cell){ constdiscussions=[]; constlinks=cell.querySelectorAll("a"); links.forEach(link=>{ consttypeIcon=link.previousElementSibling?.querySelector("img[alt]"); consttype=typeIcon?typeIcon.getAttribute("alt"):"General"; constdiscussionLink=link.getAttribute("href"); // If cite-note, fetch the links from the corresponding citation note if(discussionLink&&discussionLink.startsWith("#cite_note-")){ constnoteId=discussionLink.replace("#",""); constcitationLinks=parseCitationLinks(noteId); discussions.push(...citationLinks); }else{ // Check that the link has text content // otherwise, it is likely an icon and can be skipped if(link.textContent.length){ discussions.push({ link:discussionLink.startsWith("/")?`https://en.wikipedia.org${discussionLink}`:discussionLink, type:type, display:"inline", label:link.textContent.trim() }); } } }); returndiscussions; } /** * Converts the HTML content of a cell to Wikipedia wikitext markup. * @param {Element} cell - The table cell element. * @returns {string} The wikitext representation of the cell's content. */ functionconvertHtmlToWikiMarkup(cell){ constwikiMarkup=Array.from(cell.childNodes).map(node=>{ if(node.nodeType===Node.ELEMENT_NODE){ if(node.tagName==="A")return`[[${node.getAttribute("href").replace("/wiki/","")}|${node.textContent}]]`; if(node.tagName==="I")return`''${node.textContent}''`; if(node.tagName==="B")return`'''${node.textContent}'''`; } returnnode.textContent; }).join(""); returnwikiMarkup.trim(); } /** * Extracts all domain strings from a domains cell. * @param {Element} cell - The table cell element. * @returns {Array<string>} Array of domain strings. */ functionfindDomains(cell){ constdomains=Array.from(cell.querySelectorAll("a")).map(link=>{ constdomainMatch=link.href.match(/insource:%22([^"]+)%22/); returndomainMatch?domainMatch[1]:""; }).filter(Boolean);// Remove empty entries returndomains; } /** * Parses a citation note to extract discussion links and their context. * @param {string} noteId - The ID of the citation note element. * @returns {Array<Object>} Array of discussion objects from the citation note. */ functionparseCitationLinks(noteId){ constcitationLinks=[]; constnoteElement=document.getElementById(noteId); if(noteElement){ constreferenceText=noteElement.querySelector(".reference-text"); if(referenceText){ constlinks=Array.from(referenceText.querySelectorAll("a")); constcontextMatches=[]; letcurrentContext=""; letaccumulatingContext=false; referenceText.childNodes.forEach(node=>{ // Most citation notes have a structure like "See these discussions of <source>:", // from which we want to extract those links to discussions, // so we check for the existence of " of ": if(node.nodeType===Node.TEXT_NODE&&node.textContent.includes(" of ")){ currentContext=""; accumulatingContext=true; lettextAfterOf=node.textContent.split(" of ")[1]||""; // Extract the content after the colon, if it exists if(textAfterOf){ constcolonIndex=textAfterOf.indexOf(":"); if(colonIndex!==-1){ currentContext=textAfterOf.slice(0,colonIndex).trim(); contextMatches.push({context:currentContext.trim(),node}); accumulatingContext=false; }else{ currentContext=textAfterOf.trim(); } } // Some citation notes have multiple text nodes, // covering multiple contexts // e.g. arXiv and bioRxiv if(accumulatingContext){ letnextNode=node.nextSibling; while(nextNode&&accumulatingContext){ if(nextNode.nodeType===Node.TEXT_NODE){ constcolonIndex=nextNode.textContent.indexOf(":"); if(colonIndex!==-1){ currentContext+=" "+nextNode.textContent.slice(0,colonIndex).trim(); contextMatches.push({context:currentContext.trim(),node:nextNode}); accumulatingContext=false; }else{ currentContext+=" "+nextNode.textContent.trim(); } }elseif(nextNode.nodeType===Node.ELEMENT_NODE&&nextNode.tagName==="I"){ currentContext+=" "+nextNode.textContent.trim(); } nextNode=nextNode.nextSibling; } } } }); constmultipleContexts=contextMatches.length>1; letcurrentContextIndex=0; currentContext=contextMatches[currentContextIndex]?.context.trim()||""; links.forEach(link=>{ // Check that the link has text content // otherwise, it is likely an icon and can be skipped if(link.textContent.length){ constnextContextNode=contextMatches[currentContextIndex+1]?.node; if(nextContextNode&&link.compareDocumentPosition(nextContextNode)&Node.DOCUMENT_POSITION_PRECEDING){ if(contextMatches[currentContextIndex+1]){ currentContextIndex++; currentContext=contextMatches[currentContextIndex].context.trim(); } } constdiscussionLink=link.getAttribute("href"); letlabel=link.textContent.trim(); if(multipleContexts&¤tContext){ label+=` (${currentContext})`; } consttypeIcon=link.previousElementSibling?.querySelector("img[alt]"); consttype=typeIcon?typeIcon.getAttribute("alt"):"General"; citationLinks.push({ link:discussionLink.startsWith("/")?`https://en.wikipedia.org${discussionLink}`:discussionLink, type:type, display:"footnote", label:label }); } }); } }else{ console.warn(`[RSP-to-JSON] No element found for citation note ID: ${noteId}`); } returncitationLinks; } /** * Removes the 'discussions' field from each source object in the array. * @param {Array<Object>} sources - Array of source objects. * @returns {Array<Object>} New array with 'discussions' removed from each source. */ functionfilterOutDiscussions(sources){ returnsources.map(source=>{ const{discussions,...rest}=source; returnrest; }); } /** * Initializes the dropdown UI and handles copy-to-clipboard actions for the perennial sources table. */ functioninit(){ consttable=document.querySelector('.perennial-sources'); if(!table){ return; } // Create container div for dropdown constcontainer=document.createElement('div'); container.style.float='right'; container.style.marginBottom='10px'; container.style.marginTop='10px'; // Create select element constselect=document.createElement('select'); select.classList='cdx-select'; select.style.padding='8px'; select.style.borderRadius='2px'; // Add default option constdefaultOption=document.createElement('option'); defaultOption.value=''; defaultOption.textContent='Copy JSON...'; defaultOption.disabled=true; defaultOption.selected=true; select.appendChild(defaultOption); // Add copy options constoptions=[ {value:'with-discussions',text:'Copy with discussions'}, {value:'without-discussions',text:'Copy without discussions'} ]; options.forEach(option=>{ constoptElement=document.createElement('option'); optElement.value=option.value; optElement.textContent=option.text; select.appendChild(optElement); }); // Add elements to container container.appendChild(select); // Add documentation link below the select constdocLink=document.createElement('a'); docLink.href='https://en.wikipedia.org/wiki/User:SuperHamster/RSP-to-JSON'; docLink.textContent='RSP-to-JSON Documentation'; docLink.target='_blank'; docLink.style.display='block'; docLink.style.fontSize='11px'; docLink.style.marginTop='2px'; docLink.style.color='#3366cc'; docLink.style.textDecoration='underline'; docLink.style.textAlign='right'; container.appendChild(docLink); // Clear float for table table.style.clear='both'; // Insert container before table table.parentNode.insertBefore(container,table); select.addEventListener('change',async()=>{ try{ letresult=parsePerennialSourcesTable('perennial-sources'); if(!result||result.length===0){ console.error(`[RSP-to-JSON] Failed to produce JSON`); select.style.backgroundColor='#f9dde9'; }else{ if(select.value==='without-discussions'){ result=filterOutDiscussions(result); } awaitnavigator.clipboard.writeText(JSON.stringify(result)); select.style.backgroundColor='#dbf3ec'; } }catch(error){ console.error('Failed to copy JSON to clipboard:',error); select.style.backgroundColor='#f9dde9'; } // Reset select to default after 2 seconds setTimeout(()=>{ select.style.backgroundColor=''; select.value=''; },2000); }); } if(document.readyState==='loading'){ document.addEventListener('DOMContentLoaded',init); }else{ init(); }