Module:Cite source
- visit Module:Cite_source/doc to edit this page
Module:Cite source is the Lua module that forms the guts of {{cite source}}. You should aim to be familiar with Lua before editing it.
Broadly, the module is broken up into 2 major sections. The first is for formatting and storing information gathered from {{Infobox Story SMW}}. The second is for acessing this information and producing citations. The main method used by this module for storing data is LuaCache with Semantic MediaWiki as a backup. Variables is used for caching queries on individual pages.
The module opens by importing other util libraries. Currently, these are named in snake_case for some reason but this may be changed to camelCase in future. PREFIX is a constant used by Lua Cache and Variables to name the data stores used by the module. In the case of something going seriously wrong, incriment the number in the prefix. This will reset all data stored by the module. The p table stores the functions returned by the module. These are accesible by the wider wiki. The h table stores the functions only used by other functions in this module.
Following this, there is a short function taken from Stack Overflow that inverts tables, swapping keys and values.
Generating and saving citations
The first major part of this module deals with gathering information from {{Infobox Story}}, formatting it into citations and saving it. The main functions here are p.infoboxStore and p.variantStore, the others being called from these 2. The general idea is that each of these 2 functions put together a table containing raw data from the infobox that is then processed by various helper functions to produce the output. These will now be discussed in more detail in the same order as in the module.
h.getAndFormatUnlinkedItems
This function deals with anything that is enterred into the infobox unlinked and comma-separated in the case of multiple entries. Namely, these are the source's anthology, writer, publisher, network and the source that it was adapted from. None of these will be aplicable to every source but they are all handled at the same time by this function.
The function takes as an argument the full table of infobox arguments. A data table is then created with blank entries for each piece of data handled by this function. The keys in this table must match the corresponding keys in the args table and hence the names of the parameters in {{Infobox Story}}.
The function then iterates through each entry in this data table and checks if there's a non-empty corresponding entry in the args table. This way, it processes only the entries in the data that actually have information in the infobox. The potentially comma-separated string passed to the infobox is then converted into a table. This occurs whever there are actually multiple values for infobox field or not. Either way, we get a table, even if it only has 1 entry.
If one of the values contains "unknown", "various" or "unclear", this item is not automatically linked before being added to the table. Otherwise, the item is.
If this table has more than 4 items, only the first will be assigned to the relevant entry in the data table, followed by "et al.". This phrase is wrapped in a span with the other values in the table placed in the span's title attribute, displaying them when "et al." is hovered over on desktop. Otherwise (in most cases), all values will be listed in the relevant data table entry. Each of these are linked with the display text set to the undabbed page name.
The data table is then returned. This table will be added to by subsequent functions before being converted into an output string.
h.getAndFormatIssuesText
Following this is a function which formats issues for comics. It works very similarly to the above but without links (and without the whole "et al." thing when more than 4 items are given).
h.getAndFormatSeriesText
This function creates the bit of text stating the source's series. There are multiple places that this could come from in the infobox, none of which are ideal or one-size-fits-all, which are dealt with here. Therefore, it takes the args table as an argument.
The simplest case is if citation series is specified. This is an overide field that allows the exact series to be used to be specified. No further formatting occurs here.
The slightly more complex fallback is if range is specified. In this case, a check is first made to see if the range is an abreviation that needs to be expanded. Currently, this is done with wikitext copied from the infobox and needs converting into a format similar to the series overrides (see below). Otherwise, the field is linked and that is used.
Failing this, series is used. This infobox field is primarily used for navigation and so is unideal but is often the best that we have. The overrides (not variable is a data module, Module:Cite source/series overrides, that holds common overrides that should be applied automatically. For example, "[[Doctor Who television stories|''Doctor Who'' television stories]]" is very commonly given in the series infobox field and this data module contains a rule to automatically convert this to "''[[Doctor Who]]''". If an override does exist, it is used. Otherwise, the raw text passed to series is used. series is a freeform variable which should already have links and hence no additional formatting is applied. Next, the season number entry in the args table is checked and, if this exists, the season/series is added to the end of the series text. Finally, the special entry in the args table is checked and, if it exists, it's appended to the end of the series text, all <br/>s, <br />s and <br>s being replaced by " and ".
The series text produced is then returned. Note that, if no series is specified anywhere, this may be blank and this is fine.
h.getReleaseYearText
This function formats the release year presented in the infobox. This should be presented unlinked in accordance with {{date link}} and hence a lot of formatting must be done here.
Firstly, all date fields from the infobox are combined, using the first non-empty field in the following list:
- release date
- broadcast date
- premiere
- beta release date
- cover date
- opening date
These fields are run through util_link.stripDab in order to remove parenthetical notes and are then split on "-" in order to separate the start and end release dates of serialised sources, these 2 dates being placed in a table. For stage plays and experiences, closing date is combined with opening date.
If this table has 2 or more entries, it can be assumed that there is both a release date and a release end date. These are extracted and assigned to their respective variables. :string.match(args["release date"][1], "%d%d%d%d") extracts a string of 4 numerical digits which should be the date. A somewhat redundant check is then made that the extracted dates are actually numeric. This is done by converting it to a number and, if it can't, the dates are readjusted accordingly.
If the table only has 1 entry, the source only has a single release date and the same process as above occurs on this, without the check to see if the year is a number.
The release date(s) are then formatted with links added and the resulting string is returned.
h.makeOutput
This is a key function in this half of the module which combines all of the processed data from all previous functions into a cohesive string of text. It takes as arguments a data table containing all of the processed data produced by the previous functions and an args table containg the raw arguments from the infobox.
If data["writer"] is non-empty, this is added to the output. A check is then made to see if data["adapted from"] is non-empty. If it is, this to is added to the output. Moreover, a SMW query is used to check if the source from which the current source was adapted from has a writer. If it does, this added to the output in brackets. This involves effectively performing h.getAndFormatUnlinkedItems on the returned result in order to deal with cases where there are multiple original writers. If there was no writer, this bit is skipped and only the "adapted from" information is added to the output.
The remaining data is then formatted so that most of it is placed in brackets with the first piece of data outside of the brackets. For example, "Doctor Who season 1 (BBC tv, 1963-1964)". To achieve this, all possible pieces of data are looped through and, if they exist, they are added to a table called loopData. It is at this stage that italics are added to the source's anthology, if it was published within one. The remainder of the output is then built up through a series of if statements.
Following this, if the outputText variable is, for whatever reason, blank, it is set to an error message. Otherwise, a full stop is added. outputText is then returned.
h.storeText
This little function deals with storing the output of this half of the module. The first line stores it to Extension:Variables. The next line deletes any previous text for this source stored in Extension:LuaCache and the line following this stores the up to date output to LuaCache. The final line stores the output to Extension:Semantic MediaWiki in Property:Story info.
This function returns nothing.
p.infoboxStore
This function is the main way in which all of those discussed so far will be called. It is integrated into {{Infobox Story SMW}} and will execute on any pages with this infobox. It is responsible for combining all prior functions to take all of the arguments from the infobox and process them into a cohesive string of text which is then stored.
It takes as an argument the frame object. If you are unsure what this is, have a look at this blog post. The arguments passed to the infobox are then extracted and assigned to the args table. The next line takes the first unamed argument passed to the module when it is invoked in the template. This is not the first argument passed to the infobox, rather something hardcoded and passed into the module directly by the template. This first unamed argument will be {{PAGENAME}}, a magic word that evaluates to the name of the page that the infobox is used on. The outputText variable is initialised as an empty string.
Following this, the citation text argument is checked and, if it exists, pretty much everything else is skipped and just this is used for the output.
Otherwise, a full citation is put together. Firstly, the separator argument is checked. This is used for whenever there is a list of multiple items. They aresplit on commas by default but other separators can be set if necassary, such as if commas appear in one of the items in the list.
The arguments passed into the various formatting functions documented above are actually not entirely the raw arguments passed into the infobox. A bit of pre-processing occurs here first. Firstly, anthology and audio anthology are combined. The same happens to adapted from, adapted from2, adapted from3 and novelisation of.
Following this, the bulk of the processing to produce the finished additional info occurs by calling each of the functions documented above. The data table is created by h.getAndFormatUnlinkedItems(args) and then added to by each of the other 2 functions. Note the commented out line. This information is not currently included in the additional information but may be reincluded in the future.
Following this, h.makeOutput is run and the result of this is passed to h.storeText to be stored. Nothing is returned from the function.
p.variantStore
This function is similar to the one above and is used to produce citations for variants of a source that are covered on the same page as the main version, such as animated reconstructions, narrated soundtracks or audiobook readings. It is called solely from {{store variant data}}.
This function begins nearly identically to the one above.
Following the combination of anthology and audio anthology, and adapted from, adapted from2, adapted from3 and novelisation of, the release year and release date variables are combined. Note that, while only the year is required, the full date can be specified, hence this step. Each of the processing functions discussed above are then called, only with the processed data being saved into the variantData table instead of the data table.
Next data["variant"] is defined. This is the name by which the variant citation will be accessible through {{cite source}} and will also be displayed in the finished citation.
Following this, the finished citation text is generated. First, the string in data["variant"] has its first letter capitalised. Then, a table called KEYS is defined, containing the keys of all pieces of data from variantData to be included in the final output. These are then iterated through and the corresponding keys in variantData are checked. A comma separated list is then generated and, if this list is not empty, it is surrounded by brackets and added to the finished citation. Finally, the finished citation generated by p.infoboxStore is retrieved from variables and added. The finished citation is then stored through h.storeText.
Nothing is returned from this function.
Displaying citations
The second major part of this module deals with requesting the stored citation for the provided source and adding to that all of the additional information given when {{cite source}} is used. It's main function is p.displayCitation which is called from within {{cite source}}. The output of this function is then used by the JavaScript gadget MediaWiki:Gadget-cs.js to display the actual citation, involving calling p.generateCollapsibleTextFromJSON. This method of using JavaScript reduces the server-side processing time, significantly improving performance. p.generateCollapsibleTextFromJSON calls h.generateCollapsibleText which itself calls the other 2 helper functions.
Each function will now be discussed in the order they're presented in the function.
p.displayCitation
This function is called from {{cite source}} and deals with the part of the citation that's not inside the collapsible and so is always visible. It also prepares the collapsible to be further processed through JavaScript. It begins by setting up the frame and initialising some basic variables. The name argument is then checked. If it has a value, this citation is either referencing a previous named citation or defining a new one. If it is referencing a previous citation, this is used as the finishedCitation which completely bypasses the need for any JavaScript processing. Otherwise, a new citation is generated from the information provided.
The first unnamed argument is checked. This should contain the source that is being cited's name. If it doesn't exist, an error is returned. Otherwise, linking brackets are stripped. These are allowed to be present for linking autosuggest.
Then, the notital argument, a miss-spelling of notitle that was present in earlier versions of the template, is merged into notitle so ensure no old citations stop working.
The link's additionalDisplay text is then dealt with. This is shown as part of the link's display text alongside the source's name. There are a number of things that can be included in it which are checked in a series of if elseif statements. The only complication is the notitle argument which causes the source's title not to display. If this argument is set, colons are not included for multipath source details.
The section of the page being linked to is then dealt with, again using an if elseif statement.
If the a named part or episode is being cited, this is then checked for. Italics are included by default but are removed if necessary. This is achieved using the ital variable which is always concatenated into the output but can either contain "''" or nothing. Similarly, quote is blank by default but is set to """ (a quotation mark symbol) if necessary. If there is a named episode or part being cited, this is set as the display text. Otherwise, the user-defined display text in the second unamed argument is used. There is then an edge case if the story being cited is A Bloody (And Public) Domaine. Due to the brackets in the story's name, this must be handled seperately. A check is then made to see if the story is from a series like Doctor Who? where stories do not have unique titles and are usually cited like "COMIC: Doctor Who? 100". This is done by checking if the title, without its dab term, matches one of the keys in the earlier defined UNNAMEDMAGSTORIES table. If it does, the issue number is extracted and added to the display text. Otherwise, the undabbed page name of the cited source is used. Overiding all of these, if the notitle argument is set, no tital is display. In any case, the additionalDisplay is concatenated on. The pretext variable is also built here, displayed before the main bit of the collapsible text and containing information on where the part or episode being cited is from.
The next block of code prepares the block of JSON that the JavaScript code will use to load the citation on the client. If the noinfo is set, then there is no need for the collapsible so collapsibleText is left blank, causing the collapsible to not display (assuming there is no preciseCite. Following this, minArgs is set to args. In the current version of this code, this is useless but, in the commented-out code, arguments that weren't actually used in this citation were removed in order to reduce the size of the outputed JSON. However, this took too much processing time so was removed. It has been kept in the comment in case it can be re-incorporated at a later date.
This minArgs is then converted to JSON which is stored in minArgsJson. This is then wrapped in a <span> with classes that allow it to be detected and acted on by the JavaScript and placed in the collapsibleText variable.
If the debug argument is set, then the collapsibleText variable is instead replaced with just the JSON, allowing for easier debugging.
If the noinfo argument was set, h.generatePreciseCite(args) is called and the collapsibleText variable is set to that. A full stop is added if needed.
If the resulting collapsibleText is not blank, it's wrapped in a collapsible element where the unique class and ID pair to make the [+] uncollapse the collapsible involves a number that increments automatically with every use of the template using Variables. To do this, "<id>" is placed in the output string where the ID number should go. This number is then determined with Lua laterand all instances of "<id>" are replaced with the resulting number. If there is no collapsible text to display, an output without the collapsible is produced instead.
If a named citation is being defined, that is done here. Then, the debug argument wikitextoutput is checked. If it is set, the output is provided as pure, unprocessed wikitext. Finally, the ID for the collapsible element is then generated and added to the output. The finishedCitation is then returned.
h.generatePreciseCite
This function takes a number of arguments providing information to make citations precise and formats this into a string which can be appended to the output. It starts by defining preciseCite as an empty string. This variable will be added to and returned at the end.
What follows is a series of if statements to put everything together. The top level if checks if the precisecite argument is provided to the {{cite source}} template call. If it is, this overides all other arguments and forms the entirety of the precise cite text. Otherwise, a table is defined with keys for each piece of information that can be included in this output. Each of these potential pieces of information is then gone through in order:
- The ed argument is for the source's edition.
- The chapt argument is used when providing just a named chapter without chapter numbers. If chapter numbers are provided, these should be in the chaptnum argument and can be suplemented with a chapter name provided as chaptname. For chaptnum, a few characters are checked for in the input string. If these are present, a pluralising "s" is added to the output.
- The page argument is for the page number and uses the same pluralisation method as chaptnum.
- The timestamp argument is for custom timestamps and overides all other timestamp-related arguments. Otherwise, a table is defined with keys for each of the 3 values that can form the timestamp. The parameters for each of these are then checked for with if statements. If at least one of them exists, they are iterated through in a loop and formatted into a string. Pluralising "s"s are added unless the value is either "1" or "one".
The pieces of information in the table are then iterated through, added to the output deliminated by "; ". Finally, the trailing "; " is removed and the finished string is returned.
h.getInfo
This function deals with requesting the stored citation text. The dataStore variable is the key used across each method of storing data to identify the source, or (where relevant) the variant of a source.
The first place that is checked is Variables. This is local to the page and very fast, used if the citation has already been fetched elsewhere in the page. Failing that, LuaCache is checked. If it is available here, Variables is set. If, for whatever reason, LuaCache does not have the required data, Semantic MediaWiki is checked as a failsafe. This option is more complicated so requires some depth.
Semantic MediaWiki cannot have multiple keys per page to store variants. The way variants can be detected is by checking the data type of the SMW result. If it is a table, it includes variants. This is fine if there is only one variant but it is very hard to distinguish multiple variants so the code currently doesn't. This should come up so rarely, however, that it is likely fine to never fix this. If it is the case, an error is given. If the data type is string, we have no variants to worry about and things are simple. Regardless of the result, the requested data is saved to Variables and LuaCache.
If data was unable to be found in any location, an error is given. Finally, the info variables containing the requested info is returned.
h.generateCollapsibleText
This function deals with generating the bulk of the actual text for the collapsible section. First, the story being worked with is extracted from the first unnamed argument. Then, h.generatePreciseCite is called and its output saved to preciseCite.
Then any overrides are checked for and handled. If the citationtext argument is set, this is used, completely overriding whatever citation text was generated from the story's infobox. Otherwise, any arguments that overide individual pieces of data in the citation are checked for and, if any are present, the other pieces of data are requested through Semantic MediaWiki and a new citation is built up using the same functions as are discussed earlier. This process involves building up a new SMW query for the data not overidden, cleaning up the result of this query and formatting the results.
However, in the majority of cases, no overides will be provided and none of this will happen. In these cases, h.getInfo is called to retrieve the citation saved in the source's infobox. By default, this output will be run through NWLH.nonWLHText, a function from Module:Non-WLH link that makes links not show up in Special:WhatLinksHere. However, if the variable from the Variables extension CS-NWLH has been set to the string "false", this will not occur. The collapsibleText is then built by combining this with the preciseCite text.
Then, if the collapsibleText does not end with a ".", one is added. The collapsibleText is then returned.
p.generateCollapsibleTextFromJSON
This is the final function in this module and is only called from the MediaWiki:Gadget-cs.js JavaScript gadget. When a citation is expanded, this JavaScript will find the JSON from the csd class and pass it into this function. This is then extracted from the frame object and decoded back into a Lua table. This is then passed to h.generateCollapsibleText and the output of that returned back to the JavaScript. The JavaScript then removes the loading message and places the proper citation in. The citation is then saved to the browser's sessionStorage meaning that further uses of this citation in this tab will use this saved citation rather than calling this function again.
The final thing to occur in the module is that the p table, containing all of the functions intended to be executed through templates, is returned.
- visit Module:Cite_source/doc to edit this page
Authors and acknowledgements
This module was primarily authored by User:Bongolium500. He would like to thank the frequenters of the #lua and #smw-on-fandom channels on the Fandom Discord server for their invaluable advice and contributions. In particular, User:RheingoldRiver enabled extensions on this wiki to facilitate this project (she used to be a Wiki Representative), reviewed earlier versions of the code and helped talk through ideas to get this project finished.
Thanks must also be given to every editor who ever provided feedback on this project and to User:Scrooge MacDuck who permitted Bongolium to test {{Infobox Story SMW}} before he was an admin.
Finally, acknowlegement must be given to the authors and contributors of each of the modules imported at the top of this module, as well as to lhf on Stack Overflow for their table inverting function.
Note to fututre contributors: please add your own name and that of any others who helped you here.
Code
local concat
local dump
local print
local p = {} --p stands for package
function p.getinfo( frame )
-- local text = ""
local queryResult = mw.smw.ask( [=[ [[Rose (TV story)]] || [[Rosa (TV story)]]
|?Publisher
|?Writer
|?Release date
|format=broadtable
|limit=50
|offset=0
|link=all
|sort=
|order=asc
|headers=show
|searchlabel=... further results
|class=sortable wikitable smwtable ]=])
if queryResult == nil then
return "(no values)"
end
if type( queryResult ) == "table" then
return '<pre>' .. dump(queryResult) .. '</pre>'
end
return text
end
return p