Jump to content
Wikipedia The Free Encyclopedia

User:SuperHamster/rsp-to-json.js

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 user script seems to have a documentation page at User:SuperHamster/rsp-to-json.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
 /**
  * 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&&currentContext){
 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();
 }

AltStyle によって変換されたページ (->オリジナル) /