User:DaxServer/BooksToSfn.js
Appearance
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:DaxServer/BooksToSfn. |
//<nowiki>
/**
* –––––
* YOU ARE FULLY RESPONSIBLE FOR PUBLISHING EDITS USING THIS SCRIPT
* –––––
*
* This script is not ready for use, unless of course, if you know what you are doing.
* You must verify if the conversion is successful and modify if not.
* Recommended to also use the [[User:Trappist the monk/HarvErrors]] script in conjunction.
*/
$.when(
$.ready
).then(function () {
// Only on main namespace or sandbox
if ( ! [0, 2].includes(mw.config.get('wgNamespaceNumber'))) {
return
}
let articleName = mw.config.get('wgPageName')
articleName = encodeURIComponent(articleName); // fix bug involving & not getting converted to &
let pageIsSandbox = articleName.match(/sandbox$/)
// Only in sandbox in user namespace
if (2 === mw.config.get('wgNamespaceNumber') && ! pageIsSandbox) {
return
}
let notifyTitle = 'Books-to-Sfn'
// Activate portlet when VE source editor is enabled
mw.hook( 've.activationComplete' ).add(function () {
// Remove portlet when VE visual editor is enabled
if (0 === $('.ve-ui-surface-source').length) {
$('#ds-books-to-sfn').remove()
return
}
$.when(
mw.loader.using( [ 'mediawiki.util' ] )
).then( function () {
main()
})
})
// Remove portlet when VE is deactivated
mw.hook( 've.deactivationComplete' ).add(function () {
$('#ds-books-to-sfn').remove()
})
function main() {
const node = mw.util.addPortletLink('p-tb', '#', 'Books to Sfn', 'ds-books-to-sfn', 'Convert {{cite book}} to {{Sfn}}')
$( node ).click(function (e) {
let books = []
let textBox = $('#wpTextbox1')
let match, page, cite
let errors = 0
let index = 0
let errorMatches = []
if ( ! checkIfRefSectionExists(textBox)) {
return
}
// ToDo Add support for quotes?
while (true) {
match = getNextMatch(index)
if (null === match) {
break
}
// Determine author names
let authorNames = determineAuthorNames(match[2], 'last')
console.log('Last name(s)', authorNames)
if (0 === authorNames.length) {
authorNames = determineAuthorNames(match[2], 'author')
console.log('Author(s)', authorNames)
}
if (0 === authorNames.length) {
authorNames = determineAuthorNames(match[2], 'editor')
console.log('Editor(s)', authorNames)
}
if (authorNames.length > 4) {
console.error('More than four authors found. Please fix it manually.', authorNames, match)
// mw.notify('More than four authors found. Please fix it manually.', {
// type: 'error',
// title: notifyTitle,
// })
errors++
errorMatches.push({
error: 'More than four authors found',
match,
})
// Increment index as we need the next match
index = match.index + 1
break
}
if (0 === authorNames.length) {
console.error('Last name(s) / author(s) / editor(s) could not be determined. Please fix it manually.', authorNames, match)
// mw.notify('Last name(s) and author(s) could not be determined. Please fix it manually.', {
// type: 'error',
// title: notifyTitle,
// })
errors++
errorMatches.push({
error: 'Last name(s) / author(s) / editor(s) could not be determined',
match,
})
// Increment index as we need the next match
index = match.index + 1
continue
}
// End author names
// Determine year
let year = determineYear(match[2])
console.log('Year', year)
if (null === year) {
console.error('Year could not be determined. Please fix it manually.', match)
// mw.notify('Year could not be determined. Please fix it manually.', {
// type: 'error',
// title: notifyTitle,
// })
errors++
errorMatches.push({
error: 'Year could not be determined',
match,
})
// Increment index as we need the next match
index = match.index + 1
break
}
// Determine page
({page, cite} = determinePage(match[2]))
console.log('Page(s)', page)
console.log(cite)
// Duplicate Refs to replace with Sfn
duplicateRefs(textBox, match[1], match[0])
// Replace with Sfn
replaceWithSfn(textBox, authorNames, year, page, match[0])
books.push(cite)
// One conversion per click
break
}
if (0 === books.length) {
if (errors > 0) {
mw.notify(`First error: ${errorMatches[0]['error']}. Please fix it manually.`, {
type: 'error',
title: `${notifyTitle}: ${errors} error(s)`,
autoHideSeconds: 'long',
})
// Highlight the first error
let content = textBox.textSelection('getContents')
textBox.textSelection('setSelection', {
start: content.indexOf(errorMatches[0]['match'][0]),
end: content.indexOf(errorMatches[0]['match'][0]) + errorMatches[0]['match'][0].length,
})
console.log(errorMatches)
} else {
mw.notify('No books to convert', {
title: notifyTitle,
})
}
return
}
// Create Bibliography sub-section under References
createBiblioSectionIfNotExists(textBox)
// Add Sfns to Bibliography section
addBooksToBibliography(textBox, books)
// Hook to add edit summary
mw.hook( 've.saveDialog.stateChanged' ).add(prefillEditSummary)
e.preventDefault()
})
}
const matchRe = /<ref(?: name=["']?(:?[\w\s]+)["']?)?> *(\{\{cite book\s?\|[|\w\s=?-–-—&'#.:+,%\/[\]()]+\}\})<\/ref>/imu
const yearRe = /(?<=year\s*=)([\w\s]*)(?=\||}})/im
const dateRe = /(?<!access-?)date\s*=([\w\s-]*)(?=\||}})/im
const pageRe = /\|\s*page\s*=([\w\s]*)(?=\||}})/im
const pagesRe = /\|\s*pages\s*=([\w\s–]*)(?=\||}})/im
const reflistRe = /==\s?References\s?==\s*{{Reflist(\|.*)?}}/im
const biblioRe = /(===? ?Bibliography ?===?\s?\{\{Refbegin(\|.*)?\}\}\s(?:\*\s\{\{cite book[|\w\s=?-–-&'#.:+,%\/[\]()]+\}\}\s){0,})\{\{Refend\}\}/imu
function getNextMatch(index) {
console.log('Index for match to start from', index)
const content = $('#wpTextbox1').textSelection('getContents')
if ( ! matchRe.test(content.substring(index))) {
console.log('No matches')
return null
}
return matchRe.exec(content.substring(index))
}
function determineAuthorNames(cite, type) {
let match
let names = []
let nameReStr = `\\|\\s*${type}\\s*=([\\w\\s\\.]*)(?=\\|\|\\}\\})`
let re = new RegExp(nameReStr, 'imu')
console.log(cite)
// Determine name without index
if (re.test(cite)) {
match = re.exec(cite)
names.push(match[1].trim())
}
let nameIndex = 1
// Determine nth name with indexing
while (true) {
nameReStr = `\\|\\s*${type}${nameIndex}\\s*=([\\w\\s\\.]*)(?=\\|\|\\}\\})`
re = new RegExp(nameReStr, 'imu')
// nth name not found. No further searches for names
if ( ! re.test(cite)) {
break
}
match = re.exec(cite)
names.push(match[1].trim())
nameIndex++
}
return names
}
function determineYear(cite) {
if (yearRe.test(cite)) {
return yearRe.exec(cite)[1].trim()
}
// Determine year from date
if ( ! dateRe.test(cite)) {
return null
}
return (new Date(dateRe.exec(cite)[1])).getFullYear()
}
function determinePage(cite) {
let result = {
page: '',
cite,
}
if (pageRe.test(cite)) {
result.page = `|p=${pageRe.exec(cite)[1].trim()}`
result.cite = cite.replace(pageRe, '')
} else {
if (pagesRe.test(cite)) {
result.page = `|pp=${pagesRe.exec(cite)[1].trim()}`
result.cite = cite.replace(pagesRe, '')
}
}
return result
}
// Duplicate Refs to replace with Sfn
function duplicateRefs(textBox, refName, fullRef) {
console.log('RefName', refName)
// Ref not duplicated
if (undefined === refName) {
return
}
const reStr = `<ref name=\["']?${refName}\["']? ?\\/>`
const content = textBox.textSelection('getContents')
textBox.textSelection('setContents', content.replaceAll(new RegExp(reStr, 'imgu'), fullRef))
}
function replaceWithSfn(textBox, authorNames, year, page, fullRef) {
// page will have pipe set
let sfn = `{{Sfn|${authorNames.join('|')}|${year}${page}}}`
console.log('Sfn', sfn);
const content = textBox.textSelection('getContents')
textBox.textSelection('setContents', content.replaceAll(fullRef, sfn))
mw.notify( `Replaced ${sfn}`, {
type: 'success',
title: notifyTitle,
})
}
function checkIfRefSectionExists(textBox) {
const content = textBox.textSelection('getContents')
// Bibliography section exists
if (biblioRe.test(content)) {
return true
}
// References section exists, but not Bibliography which can be created
if (reflistRe.test(content)) {
return true;
}
// References section regex failure
mw.notify('References section not found. Possible regex failure.', {
type: 'error',
title: notifyTitle,
})
return false
}
function createBiblioSectionIfNotExists(textBox) {
const content = textBox.textSelection('getContents')
// Section exists
if (biblioRe.test(content)) {
return;
}
const reflistMatch = reflistRe.exec(content)
// Add Bibliography section
textBox.textSelection('encapsulateSelection', {
post: `\n\n=== Bibliography ===\n{{Refbegin}}\n{{Refend}}`,
selectionStart: content.indexOf(reflistMatch[0]),
selectionEnd: content.indexOf(reflistMatch[0]) + reflistMatch[0].length,
})
}
function addBooksToBibliography(textBox, books) {
const content = textBox.textSelection('getContents')
let bookRefStr = ''
for (let book of books) {
bookRefStr += `* ${book}\n`
}
const biblioMatch = biblioRe.exec(content)
textBox.textSelection('encapsulateSelection', {
post: bookRefStr,
selectionStart: content.indexOf(biblioMatch[1]),
selectionEnd: content.indexOf(biblioMatch[1]) + biblioMatch[1].length,
})
}
function prefillEditSummary() {
if (ve.init.target.saveDialog) {
ve.init.target.saveDialog.editSummaryInput.$input.val('Convert [[Template:Cite book|{{cite book}}]] reference(s) to [[Template:Sfn|Sfn]]s ([[User:DaxServer/BooksToSfn|BooksToSfn.js]])')
}
// Remove hook upon prefilling
mw.hook( 've.saveDialog.stateChanged' ).remove(prefillEditSummary)
}
})
//</nowiki>