|
|
| Line 1: |
Line 1: |
| -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]].
| | {{Permanently protected}} |
| | {{talk header}} |
| | {{WikiProject banner shell| |
| | {{WikiProject Wikidata}} |
| | }} |
| | {{tmbox|text=Use '''[[Wikipedia talk:Wikidata]]''' for general Wikidata support discussions.}} |
| | {{Lua sidebar|template=Wikidata|module=Wd}} |
| | {{User:MiszaBot/config |
| | | algo = old(90d) |
| | | archive = Module talk:Wd/Archive %(counter)d |
| | | counter = 1 |
| | | archiveheader = {{Automatic archive navigator}} |
| | | minthreadstoarchive = 1 |
| | }} |
| | {{Copied |from=Module:Wd |from_oldid=1258646059 |to=Module:European and national party data/Wd |date=22 May 2025 |to_oldid= 1291650920}} |
|
| |
|
| require("strict")
| | == Specifying URL alive/dead status == |
| local p = {}
| |
| local module_arg = ...
| |
| local i18n
| |
| local i18nPath
| |
|
| |
|
| local function loadI18n(aliasesP, frame)
| | Citations with archive URLs are displayed differently when the URL is specified as alive via {{para|url-status|live}} vs. when it's specified as dead or not specified (see first paragraph of [[Help_talk:Citation_Style_1#Treating_all_references_as_dead_by_default|here]]). Would it be possible to create a way for this template to likewise specify URL status? <span style="border:3px outset;border-radius:8pt 0;padding:1px 5px;background:linear-gradient(6rad,#86c,#2b9)">[[User:Sdkb|<span style="color:#FFF;text-decoration:inherit;font:1em Lucida Sans">Sdkb</span>]]</span> <sup>[[User talk:Sdkb|'''talk''']]</sup> 00:22, 29 July 2025 (UTC) |
| local title
| |
|
| |
|
| if frame then
| | :Yes, maybe sometime in the future, but it is not supported right now. [[User:Janhrach|Janhrach]] ([[User talk:Janhrach|talk]]) 16:20, 7 August 2025 (UTC) |
| -- current module invoked by page/template, get its title from frame
| |
| title = frame:getTitle()
| |
| else
| |
| -- current module included by other module, get its title from ...
| |
| title = module_arg
| |
| end
| |
|
| |
|
| if not i18n then
| | == Handling of Wikidata refs - empty titles and missing properties == |
| i18nPath = title .. "/i18n"
| | <!-- [[User:DoNotArchiveUntil]] 15:21, 11 May 2026 (UTC) -->{{User:ClueBot III/DoNotArchiveUntil|1778512883}} |
| i18n = require(i18nPath).init(aliasesP)
| |
| end
| |
| end
| |
|
| |
|
| p.claimCommands = {
| | We have a problem on cywiki where we have pages taking references from Wikidata. In 2023, @[[User:Janhrach|Janhrach]] very kindly forked the code and applied some additional handling to prevent visible errors. Time moves on and the code in the module has been refactored. I applied this version but it shows errors again - see https://cy.wikipedia.org/wiki/8_Mile |
| property = "property",
| |
| properties = "properties",
| |
| qualifier = "qualifier",
| |
| qualifiers = "qualifiers",
| |
| reference = "reference",
| |
| references = "references"
| |
| }
| |
|
| |
|
| p.generalCommands = {
| | I would like to keep the module up-to-date, so I had various attempts to re-apply the previous fix - but as variables have changed, it's quite tricky. There are also some more significant errors regarding those properties such as Rotten Tomatoes and Meta Critic, which is potentially easier to fix. Is there a possibility of handling this in an easier way? I would prefer not to put in special handling just for us as it limits future updates, but it would be good to know what's feasible. I could revert my changes to what was working, but I suspect that I would also need to revert other updates to Citation/CS1 modules as well. Thank you. [[User:Dafyddt|Dafyddt]] ([[User talk:Dafyddt|talk]]) 22:42, 22 August 2025 (UTC) |
| label = "label",
| |
| title = "title",
| |
| description = "description",
| |
| alias = "alias",
| |
| aliases = "aliases",
| |
| badge = "badge",
| |
| badges = "badges"
| |
| }
| |
|
| |
|
| p.flags = {
| | :Regarding the specific errors you have mentioned, the problem was that Wikidata says that references citing online databases need to contain both {{prop|248}} and a property of the {{mono|External identifier}} data type, while the references causing errors contained only the latter. This may seem like a minor technicality, but database references are handled by an ugly hack in the module, and I do not want to make this hacky code more complicated to support references like this. I plan to fix this issue by creating a separate template for citing database references; this will hopefully also eliminate cryptic error messages like these. |
| linked = "linked",
| | :As for re-introducing the cywiki fix, the old code is probably of little use, because the code has changed quite a lot. If it is really needed, I can try to reimplement it, but I would strongly prefer if you waited until I make the fix I have mentioned in the previous paragraph. |
| short = "short",
| | :Also, regarding the changes <u>to this module</u> since I made the cywiki fix, there were two of them that are especially relevant here: |
| raw = "raw",
| | :* I added verbose error messages. They should mostly be comprehensible after reading the documentation. I agree though that the errors you have mentioned are hard to understand; I will fix that. |
| multilanguage = "multilanguage",
| | :* {{mono|1={{!}}title=}} is no longer a mandatory param for {{tl|Cite web}}. In practice, this means that missing titles in references do not cause errors in this module, but in the citation template, like you see in the article you have linked. This hopefully made the errors more understandable. When I create the database-citation template, many of these missing-title errors could be eliminated. |
| unit = "unit",
| | :—[[User:Janhrach|Janhrach]] ([[User talk:Janhrach|talk]]) 14:19, 26 August 2025 (UTC) |
| -------------
| | ::{{tq|{{mono|1={{!}}title=}} is no longer a mandatory param for {{tl|Cite web}}.}} False. I don't know where you got that idea but you are mistook. {{para|title}} {{em|is}}, and likely will always be, required by {{tld|cite web}}. If you have found some place where it is stated that {{para|title}} is {{em|not}} required, name that place so that it can be visited and fixed. |
| preferred = "preferred",
| | ::—[[User:Trappist the monk|Trappist the monk]] ([[User talk:Trappist the monk|talk]]) 14:40, 26 August 2025 (UTC) |
| normal = "normal",
| | :::I meant that ''this module'' does not consider it mandatory. I wrote that {{tq|this means that missing titles in references do not cause errors in this module, but in the citation template[...]}} [[User:Janhrach|Janhrach]] ([[User talk:Janhrach|talk]]) 14:43, 26 August 2025 (UTC) |
| deprecated = "deprecated",
| | ::::Yes, I saw that, but your initial declaration that {{para|title}} is not required by {{tlx|cite web}} is false. That declaration cannot be allowed to stand unchallenged. |
| best = "best",
| | ::::—[[User:Trappist the monk|Trappist the monk]] ([[User talk:Trappist the monk|talk]]) 14:52, 26 August 2025 (UTC) |
| future = "future",
| | :::::I should have made it clear that I refered to ''my'' changes to ''this'' module. I now see that {{tq|changes since I made the cywiki fix}} could have been interpreted as including changes to CS1. [[User:Janhrach|Janhrach]] ([[User talk:Janhrach|talk]]) 14:59, 26 August 2025 (UTC) |
| current = "current",
| | ::Thanks for the reply! I will wait until your changes are made and may look at reverting our copy of Wd until that's done. [[User:Dafyddt|Dafyddt]] ([[User talk:Dafyddt|talk]]) 18:20, 26 August 2025 (UTC) |
| former = "former",
| |
| edit = "edit",
| |
| editAtEnd = "edit@end",
| |
| mdy = "mdy",
| |
| single = "single",
| |
| sourced = "sourced"
| |
| }
| |
|
| |
|
| p.args = {
| | == Language label == |
| eid = "eid",
| | <!-- [[User:DoNotArchiveUntil]] 15:21, 11 May 2026 (UTC) -->{{User:ClueBot III/DoNotArchiveUntil|1778512883}} |
| page = "page",
| |
| date = "date",
| |
| globalSiteId = "globalSiteId"
| |
| } | |
|
| |
|
| local aliasesP = {
| | Hello, could we have more customization on Language label? I mean the part when code constructs cite web etc. I see it uses <code>aliasesP.language</code> which means for Q1860 it picks "English". Maybe we could "tell" in i18n configuration to return value of ISO 639-1 or even P424 if that language has it, of course we always fallback to <code>aliasesP.language</code>. This issue comes from my local wiki, for example Module is returning label in cite web that is giving CS1 error unrecognized language. [[User:Zygimantus|Zygimantus]] ([[User talk:Zygimantus|talk]]) 13:00, 28 August 2025 (UTC) |
| coord = "P625",
| |
| -----------------------
| |
| image = "P18",
| |
| author = "P50",
| |
| authorNameString = "P2093",
| |
| publisher = "P123",
| |
| importedFrom = "P143",
| |
| wikimediaImportURL = "P4656",
| |
| statedIn = "P248",
| |
| pages = "P304",
| |
| language = "P407",
| |
| hasPart = "P527",
| |
| publicationDate = "P577",
| |
| startTime = "P580",
| |
| endTime = "P582",
| |
| chapter = "P792",
| |
| retrieved = "P813",
| |
| referenceURL = "P854",
| |
| sectionVerseOrParagraph = "P958",
| |
| archiveURL = "P1065",
| |
| title = "P1476",
| |
| formatterURL = "P1630",
| |
| quote = "P1683",
| |
| shortName = "P1813",
| |
| definingFormula = "P2534",
| |
| archiveDate = "P2960",
| |
| inferredFrom = "P3452",
| |
| typeOfReference = "P3865",
| |
| column = "P3903",
| |
| subjectNamedAs = "P1810",
| |
| wikidataProperty = "P1687",
| |
| publishedIn = "P1433",
| |
| lastUpdate = "P5017"
| |
| }
| |
|
| |
|
| local aliasesQ = {
| | :Possible? Certainly. But there other, possibly more important issues to fix first, and I do not want to devote too much time to this module. (Its creator has seemingly abandoned it.) A temporary solution would be to map aliasesP.language to an empty parameter, which would cause the aliasesP.language to be ignored<!-- line 1915 of the module-->. [[User:Janhrach|Janhrach]] ([[User talk:Janhrach|talk]]) 15:01, 5 September 2025 (UTC) |
| percentage = "Q11229",
| | ::Yeah, need to think about it. I am generally using practice for modules from enwiki like this: I treat them like upstream repo and I do not want to "fork more" than adding translations or other minor localizations. Code changes in local wiki will mean that we would deviate too much from original code and could cause many problems. [[User:Zygimantus|Zygimantus]] ([[User talk:Zygimantus|talk]]) 10:19, 10 September 2025 (UTC) |
| prolepticJulianCalendar = "Q1985786",
| | :::Yes, but the temporary workaround I proposed would imply modifying [[Module:wd/i18n]] (which is expected to be modified on other wikis), not the main module. Also, thinking about this issue, I may look into it sooner than I originally planned, because the solution I have in mind could also fix other problem, ''if it works''. But I am quite busy these days, so don't expect anything for at least a month. [[User:Janhrach|Janhrach]] ([[User talk:Janhrach|talk]]) 19:53, 17 September 2025 (UTC) |
| citeWeb = "Q5637226",
| |
| citeQ = "Q22321052"
| |
| }
| |
|
| |
|
| local parameters = {
| | == Citation wrapper templates == |
| property = "%p",
| | <!-- [[User:DoNotArchiveUntil]] 15:21, 11 May 2026 (UTC) -->{{User:ClueBot III/DoNotArchiveUntil|1778512883}} |
| qualifier = "%q",
| |
| reference = "%r",
| |
| alias = "%a",
| |
| badge = "%b",
| |
| separator = "%s",
| |
| general = "%x"
| |
| } | |
|
| |
|
| local formats = {
| | The {{mono|getReference}} method is quite cluttered and complicated. Fixing the {{mono|{{!}}language{{=}}}} issue reported above would exacerbate this problem. One solution would be to create separate wrapper templates/submodules, one for Cite web and one for Cite Q, that would take QIDs as params, and generate Cite web/Cite Q calls with the QIDs converted to the form required by the citation templates. I think separating this functionality into wrappers would result in less clutter in the {{mono|getReference}} function. Namely, this functionality would be moved into the wrapper templates: |
| property = "%p[%s][%r]",
| | * special handling for the first, unnamed param of Cite Q, which always needs to be given as a QID |
| qualifier = "%q[%s][%r]",
| | * special handling of {{mono|external-id}} properties, which are used (in some situations) to generate {{mono|{{!}}url{{=}}}} for Cite web (This would enable removing the all the {{mono|additionalProcessedProperties}}-related code cruft.) |
| reference = "%r",
| | * special handling of {{mono|{{!}}language{{=}}}} in Cite web |
| propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]",
| | * the methods {{mono|getReferenceDetail}} and {{mono|getReferenceDetails}} |
| alias = "%a[%s]",
| | * (possibly) special handling of numbered parameters, like {{mono|{{!}}author{{=}}}} of Cite web. |
| badge = "%b[%s]"
| |
| } | |
|
| |
|
| local hookNames = { -- {level_1, level_2}
| | One downside is that editors who copy this module to other wikis would need to notice the new wrapper templates/submodules, and adjust their /i18n. Failure to do the latter will not result in explicit error messages, but in weird references that contain QIDs instead of the wanted params. |
| [parameters.property] = {"getProperty"},
| |
| [parameters.reference] = {"getReferences", "getReference"},
| |
| [parameters.qualifier] = {"getAllQualifiers"},
| |
| [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"},
| |
| [parameters.alias] = {"getAlias"},
| |
| [parameters.badge] = {"getBadge"}
| |
| }
| |
|
| |
|
| -- default value objects, should NOT be mutated but instead copied
| | Another downside would be that that the option of creating references that do not contain wikilinks would need to be removed. But I can't think why would anybody need this. |
| local defaultSeparators = {
| |
| ["sep"] = {" "},
| |
| ["sep%s"] = {","},
| |
| ["sep%q"] = {"; "},
| |
| ["sep%q\\d"] = {", "},
| |
| ["sep%r"] = nil, -- none
| |
| ["punc"] = nil -- none
| |
| }
| |
|
| |
|
| local rankTable = {
| | But speaking of other wikis, this change would have an advantage of letting their editors tweak the wrappers without worrying about the need of porting changes to new versions of the main module. |
| ["preferred"] = 1,
| |
| ["normal"] = 2,
| |
| ["deprecated"] = 3
| |
| }
| |
|
| |
|
| local function replaceAlias(id)
| | What do you think? Pinging {{u|Zygimantus}}. |
| if aliasesP[id] then
| |
| id = aliasesP[id]
| |
| end
| |
|
| |
|
| return id
| | —[[User:Janhrach|Janhrach]] ([[User talk:Janhrach|talk]]) 17:31, 18 September 2025 (UTC) |
| end
| |
|
| |
|
| local function errorText(code, ...)
| | :I am ok whatever decision is, logical to have configurable on i18n side, maybe refactor how much possible, so Wd has only fetching logic but what to fetch and what to show should be controlled in a smart way using i18n. [[User:Zygimantus|Zygimantus]] ([[User talk:Zygimantus|talk]]) 13:49, 19 September 2025 (UTC) |
| local text = i18n["errors"][code]
| |
| if arg then text = mw.ustring.format(text, unpack(arg)) end
| |
| return text
| |
| end
| |
|
| |
|
| local function throwError(errorMessage, ...)
| | *I too would like to see special handling of external-id properties but going further, if a wikimedia template is given for a reference source using an external-id, WD should use that citation template instead of {{tl|cite web}} with the assumption that the wikimedia template is based on cite web and thus is typically setting parameters such as the identifier and URL. e.g. {{tl|cite cgndb}} and {{tl|cite bivouac}}. This would also fix the issues where WD is not retrieving the publisher and author. [[User:RedWolf|RedWolf]] ([[User talk:RedWolf|talk]]) 20:02, 25 October 2025 (UTC) |
| error(errorText(errorMessage, unpack(arg)))
| |
| end
| |
|
| |
|
| local function replaceDecimalMark(num)
| | == Property value with multiple references == |
| return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1)
| |
| end
| |
|
| |
|
| local function padZeros(num, numDigits)
| | If a WD property value has multiple references and you enclose the returned template value in <nowiki><ref></ref></nowiki> you end up with just one reference rather than multiple. For example, the elevation property value for Mount Robson (Q1161258) has two references but it ends up showing as a single reference but with both sources given in the one reference which is not ideal and technically incorrect as they should be displayed as separate references. |
| local numZeros
| | <br>i.e. |
| local negative = false
| | <nowiki>The elevation of [[Mount Robson]] is {{Wikidata|property|raw|Q1161258|P2044}} metres.<ref>{{Wikidata|references|raw|Q1161258|P2044}}</ref></nowiki> |
| | The elevation of [[Mount Robson]] is {{Wikidata|property|raw|Q1161258|P2044}} metres.<ref>{{Wikidata|references|raw|Q1161258|P2044}}</ref> |
| | <references/> |
| | Do I have to write a module that takes the output from <nowiki>{{Wikidata}}</nowiki> and wrap each citation with separate <nowiki><ref></ref></nowiki> or is there a template parameter that will do this for me? [[User:RedWolf|RedWolf]] ([[User talk:RedWolf|talk]]) 01:11, 22 October 2025 (UTC) |
|
| |
|
| if num < 0 then
| | :Two examples: |
| negative = true
| | :*<code><nowiki>{{Wikidata|references|raw|Q1161258|P2044}}</nowiki></code> |
| num = num * -1
| | :*:{{Wikidata|references|raw|Q1161258|P2044}} |
| end
| | :*<code><nowiki>{{Wikidata|references|Q1161258|P2044}}</nowiki></code> |
| | | :*:{{Wikidata|references|Q1161258|P2044}} |
| num = tostring(num)
| | :I am not sure if I understand you correctly, but I think that the latter example is what you want. In that case, you shouldn't use the {{mono|raw}} argument. |
| numZeros = numDigits - num:len()
| | {{reflist-talk}} |
| | | :—[[User:Janhrach|Janhrach]] ([[User talk:Janhrach|talk]]) 15:12, 23 October 2025 (UTC) |
| for _ = 1, numZeros do
| | ::Yea I tried w/o the "raw" but then the references didn't show up properly in the References section. That problem seems to have been caused by wrapping the template output with <nowiki><ref></ref></nowiki>. The documentation is not clear that it wraps each citation template it generates with <nowiki><ref></ref></nowiki>. Thanks for the help. [[User:RedWolf|RedWolf]] ([[User talk:RedWolf|talk]]) 05:40, 25 October 2025 (UTC) |
| num = "0"..num
| | :::I have clarified the documentation. [[User:Janhrach|Janhrach]] ([[User talk:Janhrach|talk]]) 17:56, 9 November 2025 (UTC) |
| end
| |
| | |
| if negative then
| |
| num = "-"..num
| |
| end
| |
| | |
| return num
| |
| end
| |
| | |
| local function replaceSpecialChar(chr)
| |
| if chr == '_' then
| |
| -- replace underscores with spaces
| |
| return ' '
| |
| else
| |
| return chr
| |
| end
| |
| end
| |
| | |
| local function replaceSpecialChars(str)
| |
| local chr
| |
| local esc = false
| |
| local strOut = ""
| |
| | |
| for i = 1, #str do
| |
| chr = str:sub(i,i)
| |
| | |
| if not esc then
| |
| if chr == '\\' then
| |
| esc = true
| |
| else
| |
| strOut = strOut .. replaceSpecialChar(chr)
| |
| end
| |
| else
| |
| strOut = strOut .. chr
| |
| esc = false
| |
| end
| |
| end
| |
| | |
| return strOut
| |
| end
| |
| | |
| local function buildWikilink(target, label)
| |
| if not label or target == label then
| |
| return "[[" .. target .. "]]"
| |
| else
| |
| return "[[" .. target .. "|" .. label .. "]]"
| |
| end
| |
| end
| |
| | |
| -- used to make frame.args mutable, to replace #frame.args (which is always 0)
| |
| -- with the actual amount and to simply copy tables
| |
| local function copyTable(tIn)
| |
| if not tIn then
| |
| return nil
| |
| end
| |
| | |
| local tOut = {}
| |
| | |
| for i, v in pairs(tIn) do
| |
| tOut[i] = v
| |
| end
| |
| | |
| return tOut
| |
| end
| |
| | |
| -- used to merge output arrays together;
| |
| -- note that it currently mutates the first input array
| |
| local function mergeArrays(a1, a2)
| |
| for i = 1, #a2 do
| |
| a1[#a1 + 1] = a2[i]
| |
| end
| |
| | |
| return a1
| |
| end
| |
| | |
| local function split(str, del)
| |
| local out = {}
| |
| local i, j = str:find(del)
| |
| | |
| if i and j then
| |
| out[1] = str:sub(1, i - 1)
| |
| out[2] = str:sub(j + 1)
| |
| else
| |
| out[1] = str
| |
| end
| |
| | |
| return out
| |
| end
| |
| | |
| local function parseWikidataURL(url)
| |
| local id
| |
| | |
| if url:match('^http[s]?://') then
| |
| id = split(url, "Q")
| |
| | |
| if id[2] then
| |
| return "Q" .. id[2]
| |
| end
| |
| end
| |
| | |
| return nil
| |
| end
| |
| | |
| local function parseDate(dateStr, precision)
| |
| precision = precision or "d"
| |
| | |
| local i, j, index, ptr
| |
| local parts = {nil, nil, nil}
| |
| | |
| if dateStr == nil then
| |
| return parts[1], parts[2], parts[3] -- year, month, day
| |
| end
| |
| | |
| -- 'T' for snak values, '/' for outputs with '/Julian' attached
| |
| i, j = dateStr:find("[T/]")
| |
| | |
| if i then
| |
| dateStr = dateStr:sub(1, i-1)
| |
| end
| |
| | |
| local from = 1
| |
| | |
| if dateStr:sub(1,1) == "-" then
| |
| -- this is a negative number, look further ahead
| |
| from = 2
| |
| end
| |
| | |
| index = 1
| |
| ptr = 1
| |
| | |
| i, j = dateStr:find("-", from)
| |
| | |
| if i then
| |
| -- year
| |
| parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error
| |
| | |
| if parts[index] == -0 then
| |
| parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead
| |
| end
| |
| | |
| if precision == "y" then
| |
| -- we're done
| |
| return parts[1], parts[2], parts[3] -- year, month, day
| |
| end
| |
| | |
| index = index + 1
| |
| ptr = i + 1
| |
| | |
| i, j = dateStr:find("-", ptr)
| |
| | |
| if i then
| |
| -- month
| |
| parts[index] = tonumber(dateStr:sub(ptr, i-1), 10)
| |
| | |
| if precision == "m" then
| |
| -- we're done
| |
| return parts[1], parts[2], parts[3] -- year, month, day
| |
| end
| |
| | |
| index = index + 1
| |
| ptr = i + 1
| |
| end
| |
| end
| |
| | |
| if dateStr:sub(ptr) ~= "" then
| |
| -- day if we have month, month if we have year, or year
| |
| parts[index] = tonumber(dateStr:sub(ptr), 10)
| |
| end
| |
| | |
| return parts[1], parts[2], parts[3] -- year, month, day
| |
| end
| |
| | |
| local function datePrecedesDate(aY, aM, aD, bY, bM, bD)
| |
| if aY == nil or bY == nil then
| |
| return nil
| |
| end
| |
| aM = aM or 1
| |
| aD = aD or 1
| |
| bM = bM or 1
| |
| bD = bD or 1
| |
| | |
| if aY < bY then
| |
| return true
| |
| end
| |
| | |
| if aY > bY then
| |
| return false
| |
| end
| |
| | |
| if aM < bM then
| |
| return true
| |
| end
| |
| | |
| if aM > bM then
| |
| return false
| |
| end
| |
| | |
| if aD < bD then
| |
| return true
| |
| end
| |
| | |
| return false
| |
| end
| |
| | |
| local function getHookName(param, index)
| |
| if hookNames[param] then
| |
| return hookNames[param][index]
| |
| elseif param:len() > 2 then
| |
| return hookNames[param:sub(1, 2).."\\d"][index]
| |
| else
| |
| return nil
| |
| end
| |
| end
| |
| | |
| local function alwaysTrue()
| |
| return true
| |
| end
| |
| | |
| -- The following function parses a format string.
| |
| --
| |
| -- The example below shows how a parsed string is structured in memory.
| |
| -- Variables other than 'str' and 'child' are left out for clarity's sake.
| |
| --
| |
| -- Example:
| |
| -- "A %p B [%s[%q1]] C [%r] D"
| |
| --
| |
| -- Structure:
| |
| -- [
| |
| -- {
| |
| -- str = "A "
| |
| -- },
| |
| -- {
| |
| -- str = "%p"
| |
| -- },
| |
| -- {
| |
| -- str = " B ",
| |
| -- child =
| |
| -- [
| |
| -- {
| |
| -- str = "%s",
| |
| -- child =
| |
| -- [
| |
| -- {
| |
| -- str = "%q1"
| |
| -- }
| |
| -- ]
| |
| -- }
| |
| -- ]
| |
| -- },
| |
| -- {
| |
| -- str = " C ",
| |
| -- child =
| |
| -- [
| |
| -- {
| |
| -- str = "%r"
| |
| -- }
| |
| -- ]
| |
| -- },
| |
| -- {
| |
| -- str = " D"
| |
| -- }
| |
| -- ]
| |
| --
| |
| local function parseFormat(str)
| |
| local chr, esc, param, root, cur, prev, new
| |
| local params = {}
| |
| | |
| local function newObject(array)
| |
| local obj = {} -- new object
| |
| obj.str = ""
| |
| | |
| array[#array + 1] = obj -- array{object}
| |
| obj.parent = array
| |
| | |
| return obj
| |
| end
| |
| | |
| local function endParam()
| |
| if param > 0 then
| |
| if cur.str ~= "" then
| |
| cur.str = "%"..cur.str
| |
| cur.param = true
| |
| params[cur.str] = true
| |
| cur.parent.req[cur.str] = true
| |
| prev = cur
| |
| cur = newObject(cur.parent)
| |
| end
| |
| param = 0
| |
| end
| |
| end
| |
| | |
| root = {} -- array
| |
| root.req = {}
| |
| cur = newObject(root)
| |
| prev = nil
| |
| | |
| esc = false
| |
| param = 0
| |
| | |
| for i = 1, #str do
| |
| chr = str:sub(i,i)
| |
| | |
| if not esc then
| |
| if chr == '\\' then
| |
| endParam()
| |
| esc = true
| |
| elseif chr == '%' then
| |
| endParam()
| |
| if cur.str ~= "" then
| |
| cur = newObject(cur.parent)
| |
| end
| |
| param = 2
| |
| elseif chr == '[' then
| |
| endParam()
| |
| if prev and cur.str == "" then
| |
| table.remove(cur.parent)
| |
| cur = prev
| |
| end
| |
| cur.child = {} -- new array
| |
| cur.child.req = {}
| |
| cur.child.parent = cur
| |
| cur = newObject(cur.child)
| |
| elseif chr == ']' then
| |
| endParam()
| |
| if cur.parent.parent then
| |
| new = newObject(cur.parent.parent.parent)
| |
| if cur.str == "" then
| |
| table.remove(cur.parent)
| |
| end
| |
| cur = new
| |
| end
| |
| else
| |
| if param > 1 then
| |
| param = param - 1
| |
| elseif param == 1 then
| |
| if not chr:match('%d') then
| |
| endParam()
| |
| end
| |
| end
| |
| | |
| cur.str = cur.str .. replaceSpecialChar(chr)
| |
| end
| |
| else
| |
| cur.str = cur.str .. chr
| |
| esc = false
| |
| end
| |
| | |
| prev = nil
| |
| end
| |
| | |
| endParam()
| |
| | |
| -- make sure that at least one required parameter has been defined
| |
| if not next(root.req) then
| |
| throwError("missing-required-parameter")
| |
| end
| |
| | |
| -- make sure that the separator parameter "%s" is not amongst the required parameters
| |
| if root.req[parameters.separator] then
| |
| throwError("extra-required-parameter", parameters.separator)
| |
| end
| |
| | |
| return root, params
| |
| end
| |
| | |
| local function sortOnRank(claims)
| |
| local rankPos
| |
| local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default)
| |
| local sorted = {}
| |
| | |
| for _, v in ipairs(claims) do
| |
| rankPos = rankTable[v.rank] or 4
| |
| ranks[rankPos][#ranks[rankPos] + 1] = v
| |
| end
| |
| | |
| sorted = ranks[1]
| |
| sorted = mergeArrays(sorted, ranks[2])
| |
| sorted = mergeArrays(sorted, ranks[3])
| |
| | |
| return sorted
| |
| end
| |
| | |
| local function isValueInTable(searchedItem, inputTable)
| |
| for _, item in pairs(inputTable) do
| |
| if item == searchedItem then
| |
| return true
| |
| end
| |
| end
| |
| return false
| |
| end
| |
| | |
| local Config = {}
| |
| | |
| -- allows for recursive calls
| |
| function Config:new()
| |
| local cfg = {}
| |
| setmetatable(cfg, self)
| |
| self.__index = self
| |
| | |
| cfg.separators = {
| |
| -- single value objects wrapped in arrays so that we can pass by reference
| |
| ["sep"] = {copyTable(defaultSeparators["sep"])},
| |
| ["sep%s"] = {copyTable(defaultSeparators["sep%s"])},
| |
| ["sep%q"] = {copyTable(defaultSeparators["sep%q"])},
| |
| ["sep%r"] = {copyTable(defaultSeparators["sep%r"])},
| |
| ["punc"] = {copyTable(defaultSeparators["punc"])}
| |
| }
| |
| | |
| cfg.entity = nil
| |
| cfg.entityID = nil
| |
| cfg.propertyID = nil
| |
| cfg.propertyValue = nil
| |
| cfg.qualifierIDs = {}
| |
| cfg.qualifierIDsAndValues = {}
| |
| | |
| cfg.bestRank = true
| |
| cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false
| |
| cfg.foundRank = #cfg.ranks
| |
| cfg.flagBest = false
| |
| cfg.flagRank = false
| |
| | |
| cfg.periods = {true, true, true} -- future = true, current = true, former = true
| |
| cfg.flagPeriod = false
| |
| cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day}
| |
| | |
| cfg.mdyDate = false
| |
| cfg.singleClaim = false
| |
| cfg.sourcedOnly = false
| |
| cfg.editable = false
| |
| cfg.editAtEnd = false
| |
| | |
| cfg.inSitelinks = false
| |
| | |
| cfg.langCode = mw.language.getContentLanguage().code
| |
| cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode)
| |
| cfg.langObj = mw.language.new(cfg.langCode)
| |
| | |
| cfg.siteID = mw.wikibase.getGlobalSiteId()
| |
| | |
| cfg.states = {}
| |
| cfg.states.qualifiersCount = 0
| |
| cfg.curState = nil
| |
| | |
| cfg.prefetchedRefs = nil
| |
| | |
| return cfg
| |
| end
| |
| | |
| local State = {}
| |
| | |
| function State:new(cfg, type)
| |
| local stt = {}
| |
| setmetatable(stt, self)
| |
| self.__index = self
| |
| | |
| stt.conf = cfg
| |
| stt.type = type
| |
| | |
| stt.results = {}
| |
| | |
| stt.parsedFormat = {}
| |
| stt.separator = {}
| |
| stt.movSeparator = {}
| |
| stt.puncMark = {}
| |
| | |
| stt.linked = false
| |
| stt.rawValue = false
| |
| stt.shortName = false
| |
| stt.anyLanguage = false
| |
| stt.unitOnly = false
| |
| stt.singleValue = false
| |
| | |
| return stt
| |
| end
| |
| | |
| -- if id == nil then item connected to current page is used
| |
| function Config:getLabel(id, raw, link, short)
| |
| local label = nil
| |
| local prefix, title= "", nil
| |
| | |
| if not id then
| |
| id = mw.wikibase.getEntityIdForCurrentPage()
| |
| | |
| if not id then
| |
| return ""
| |
| end
| |
| end
| |
| | |
| id = id:upper() -- just to be sure
| |
| | |
| if raw then
| |
| -- check if given id actually exists
| |
| if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then
| |
| label = id
| |
| end
| |
| | |
| prefix, title = "d:Special:EntityPage/", label -- may be nil
| |
| else
| |
| -- try short name first if requested
| |
| if short then
| |
| label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name
| |
| | |
| if label == "" then
| |
| label = nil
| |
| end
| |
| end
| |
| | |
| -- get label
| |
| if not label then
| |
| label = mw.wikibase.getLabel(id)
| |
| end
| |
| end
| |
| | |
| if not label then
| |
| label = ""
| |
| elseif link then
| |
| -- build a link if requested
| |
| if not title then
| |
| if id:sub(1,1) == "Q" then
| |
| title = mw.wikibase.getSitelink(id)
| |
| elseif id:sub(1,1) == "P" then
| |
| -- properties have no sitelink, link to Wikidata instead
| |
| prefix, title = "d:Special:EntityPage/", id
| |
| end
| |
| end
| |
| | |
| label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup
| |
| if title then
| |
| label = buildWikilink(prefix .. title, label)
| |
| end
| |
| end
| |
| | |
| return label
| |
| end
| |
| | |
| function Config:getEditIcon()
| |
| local value = ""
| |
| local prefix = ""
| |
| local front = " "
| |
| local back = ""
| |
| | |
| if self.entityID:sub(1,1) == "P" then
| |
| prefix = "Property:"
| |
| end
| |
| | |
| if self.editAtEnd then
| |
| front = '<span style="float:'
| |
| | |
| if self.langObj:isRTL() then
| |
| front = front .. 'left'
| |
| else
| |
| front = front .. 'right'
| |
| end
| |
| | |
| front = front .. '">'
| |
| back = '</span>'
| |
| end
| |
| | |
| value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode
| |
| | |
| if self.propertyID then
| |
| value = value .. "#" .. self.propertyID
| |
| elseif self.inSitelinks then
| |
| value = value .. "#sitelinks-wikipedia"
| |
| end
| |
| | |
| value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]"
| |
| | |
| return front .. value .. back
| |
| end
| |
| | |
| -- used to create the final output string when it's all done, so that for references the
| |
| -- function extensionTag("ref", ...) is only called when they really ended up in the final output
| |
| function Config:concatValues(valuesArray)
| |
| local outString = ""
| |
| local j, skip
| |
| | |
| for i = 1, #valuesArray do
| |
| -- check if this is a reference
| |
| if valuesArray[i].refHash then
| |
| j = i - 1
| |
| skip = false
| |
| | |
| -- skip this reference if it is part of a continuous row of references that already contains the exact same reference
| |
| while valuesArray[j] and valuesArray[j].refHash do
| |
| if valuesArray[i].refHash == valuesArray[j].refHash then
| |
| skip = true
| |
| break
| |
| end
| |
| j = j - 1
| |
| end
| |
| | |
| if not skip then
| |
| -- add <ref> tag with the reference's hash as its name (to deduplicate references)
| |
| outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash})
| |
| end
| |
| else
| |
| outString = outString .. valuesArray[i][1]
| |
| end
| |
| end
| |
| | |
| return outString
| |
| end
| |
| | |
| function Config:convertUnit(unit, raw, link, short, unitOnly)
| |
| local space = " "
| |
| local label = ""
| |
| local itemID
| |
| | |
| if unit == "" or unit == "1" then
| |
| return nil
| |
| end
| |
| | |
| if unitOnly then
| |
| space = ""
| |
| end
| |
| | |
| itemID = parseWikidataURL(unit)
| |
| | |
| if itemID then
| |
| if itemID == aliasesQ.percentage then
| |
| return "%"
| |
| else
| |
| label = self:getLabel(itemID, raw, link, short)
| |
| | |
| if label ~= "" then
| |
| return space .. label
| |
| end
| |
| end
| |
| end
| |
| | |
| return ""
| |
| end
| |
| | |
| function State:getValue(snak)
| |
| return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2))
| |
| end
| |
| | |
| function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type)
| |
| if snak.snaktype == 'value' then
| |
| local datatype = snak.datavalue.type
| |
| local subtype = snak.datatype
| |
| local datavalue = snak.datavalue.value
| |
| | |
| if datatype == 'string' then
| |
| if subtype == 'url' and link then
| |
| -- create link explicitly
| |
| if raw then
| |
| -- will render as a linked number like [1]
| |
| return "[" .. datavalue .. "]"
| |
| else
| |
| return "[" .. datavalue .. " " .. datavalue .. "]"
| |
| end
| |
| elseif subtype == 'commonsMedia' then
| |
| if link then
| |
| return buildWikilink("c:File:" .. datavalue, datavalue)
| |
| elseif not raw then
| |
| return "[[File:" .. datavalue .. "]]"
| |
| else
| |
| return datavalue
| |
| end
| |
| elseif subtype == 'geo-shape' and link then
| |
| return buildWikilink("c:" .. datavalue, datavalue)
| |
| elseif subtype == 'math' and not raw then
| |
| local attribute = nil
| |
| | |
| if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then
| |
| attribute = {qid = self.entityID}
| |
| end
| |
| | |
| return mw.getCurrentFrame():extensionTag("math", datavalue, attribute)
| |
| elseif subtype == 'external-id' and link then
| |
| local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL
| |
| | |
| if url ~= "" then
| |
| url = mw.ustring.gsub(url, "$1", datavalue)
| |
| return "[" .. url .. " " .. datavalue .. "]"
| |
| else
| |
| return datavalue
| |
| end
| |
| else
| |
| return datavalue
| |
| end
| |
| elseif datatype == 'monolingualtext' then
| |
| if anyLang or datavalue['language'] == self.langCode then
| |
| return datavalue['text']
| |
| else
| |
| return nil
| |
| end
| |
| elseif datatype == 'quantity' then
| |
| local value = ""
| |
| local unit
| |
| | |
| if not unitOnly then
| |
| -- get value and strip + signs from front
| |
| value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1")
| |
| | |
| if raw then
| |
| return value
| |
| end
| |
| | |
| -- replace decimal mark based on locale
| |
| value = replaceDecimalMark(value)
| |
| | |
| -- add delimiters for readability
| |
| value = i18n.addDelimiters(value)
| |
| end
| |
| | |
| unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly)
| |
| | |
| if unit then
| |
| value = value .. unit
| |
| end
| |
| | |
| return value
| |
| elseif datatype == 'time' then
| |
| local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr
| |
| local yFactor = 1
| |
| local sign = 1
| |
| local prefix = ""
| |
| local suffix = ""
| |
| local mayAddCalendar = false
| |
| local calendar = ""
| |
| local precision = datavalue['precision']
| |
| | |
| if precision == 11 then
| |
| p = "d"
| |
| elseif precision == 10 then
| |
| p = "m"
| |
| else
| |
| p = "y"
| |
| yFactor = 10^(9-precision)
| |
| end
| |
| | |
| y, m, d = parseDate(datavalue['time'], p)
| |
| | |
| if y < 0 then
| |
| sign = -1
| |
| y = y * sign
| |
| end
| |
| | |
| -- if precision is tens/hundreds/thousands/millions/billions of years
| |
| if precision <= 8 then
| |
| yDiv = y / yFactor
| |
| | |
| -- if precision is tens/hundreds/thousands of years
| |
| if precision >= 6 then
| |
| mayAddCalendar = true
| |
| | |
| if precision <= 7 then
| |
| -- round centuries/millenniums up (e.g. 20th century or 3rd millennium)
| |
| yRound = math.ceil(yDiv)
| |
| | |
| if not raw then
| |
| if precision == 6 then
| |
| suffix = i18n['datetime']['suffixes']['millennium']
| |
| else
| |
| suffix = i18n['datetime']['suffixes']['century']
| |
| end
| |
| | |
| suffix = i18n.getOrdinalSuffix(yRound) .. suffix
| |
| else
| |
| -- if not verbose, take the first year of the century/millennium
| |
| -- (e.g. 1901 for 20th century or 2001 for 3rd millennium)
| |
| yRound = (yRound - 1) * yFactor + 1
| |
| end
| |
| else
| |
| -- precision == 8
| |
| -- round decades down (e.g. 2010s)
| |
| yRound = math.floor(yDiv) * yFactor
| |
| | |
| if not raw then
| |
| prefix = i18n['datetime']['prefixes']['decade-period']
| |
| suffix = i18n['datetime']['suffixes']['decade-period']
| |
| end
| |
| end
| |
| | |
| if raw and sign < 0 then
| |
| -- if BCE then compensate for "counting backwards"
| |
| -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE)
| |
| yRound = yRound + yFactor - 1
| |
| end
| |
| else
| |
| local yReFactor, yReDiv, yReRound
| |
| | |
| -- round to nearest for tens of thousands of years or more
| |
| yRound = math.floor(yDiv + 0.5)
| |
| | |
| if yRound == 0 then
| |
| if precision <= 2 and y ~= 0 then
| |
| yReFactor = 1e6
| |
| yReDiv = y / yReFactor
| |
| yReRound = math.floor(yReDiv + 0.5)
| |
| | |
| if yReDiv == yReRound then
| |
| -- change precision to millions of years only if we have a whole number of them
| |
| precision = 3
| |
| yFactor = yReFactor
| |
| yRound = yReRound
| |
| end
| |
| end
| |
| | |
| if yRound == 0 then
| |
| -- otherwise, take the unrounded (original) number of years
| |
| precision = 5
| |
| yFactor = 1
| |
| yRound = y
| |
| mayAddCalendar = true
| |
| end
| |
| end
| |
| | |
| if precision >= 1 and y ~= 0 then
| |
| yFull = yRound * yFactor
| |
| | |
| yReFactor = 1e9
| |
| yReDiv = yFull / yReFactor
| |
| yReRound = math.floor(yReDiv + 0.5)
| |
| | |
| if yReDiv == yReRound then
| |
| -- change precision to billions of years if we're in that range
| |
| precision = 0
| |
| yFactor = yReFactor
| |
| yRound = yReRound
| |
| else
| |
| yReFactor = 1e6
| |
| yReDiv = yFull / yReFactor
| |
| yReRound = math.floor(yReDiv + 0.5)
| |
| | |
| if yReDiv == yReRound then
| |
| -- change precision to millions of years if we're in that range
| |
| precision = 3
| |
| yFactor = yReFactor
| |
| yRound = yReRound
| |
| end
| |
| end
| |
| end
| |
| | |
| if not raw then
| |
| if precision == 3 then
| |
| suffix = i18n['datetime']['suffixes']['million-years']
| |
| elseif precision == 0 then
| |
| suffix = i18n['datetime']['suffixes']['billion-years']
| |
| else
| |
| yRound = yRound * yFactor
| |
| if yRound == 1 then
| |
| suffix = i18n['datetime']['suffixes']['year']
| |
| else
| |
| suffix = i18n['datetime']['suffixes']['years']
| |
| end
| |
| end
| |
| else
| |
| yRound = yRound * yFactor
| |
| end
| |
| end
| |
| else
| |
| yRound = y
| |
| mayAddCalendar = true
| |
| end
| |
| | |
| if mayAddCalendar then
| |
| calendarID = parseWikidataURL(datavalue['calendarmodel'])
| |
| | |
| if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then
| |
| if not raw then
| |
| if link then
| |
| calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")"
| |
| else
| |
| calendar = " ("..i18n['datetime']['julian']..")"
| |
| end
| |
| else
| |
| calendar = "/"..i18n['datetime']['julian']
| |
| end
| |
| end
| |
| end
| |
| | |
| if not raw then
| |
| local ce = nil
| |
| | |
| if sign < 0 then
| |
| ce = i18n['datetime']['BCE']
| |
| elseif precision <= 5 then
| |
| ce = i18n['datetime']['CE']
| |
| end
| |
| | |
| if ce then
| |
| if link then
| |
| ce = buildWikilink(i18n['datetime']['common-era'], ce)
| |
| end
| |
| suffix = suffix .. " " .. ce
| |
| end
| |
| | |
| value = tostring(yRound)
| |
| | |
| if m then
| |
| dateStr = self.langObj:formatDate("F", "1-"..m.."-1")
| |
| | |
| if d then
| |
| if self.mdyDate then
| |
| dateStr = dateStr .. " " .. d .. ","
| |
| else
| |
| dateStr = d .. " " .. dateStr
| |
| end
| |
| end
| |
| | |
| value = dateStr .. " " .. value
| |
| end
| |
| | |
| value = prefix .. value .. suffix .. calendar
| |
| else
| |
| value = padZeros(yRound * sign, 4)
| |
| | |
| if m then
| |
| value = value .. "-" .. padZeros(m, 2)
| |
| | |
| if d then
| |
| value = value .. "-" .. padZeros(d, 2)
| |
| end
| |
| end
| |
| | |
| value = value .. calendar
| |
| end
| |
| | |
| return value
| |
| elseif datatype == 'globecoordinate' then
| |
| -- logic from https://github.com/DataValues/Geo (v4.0.1)
| |
| | |
| local precision, unitsPerDegree, numDigits, strFormat, value, globe
| |
| local latitude, latConv, latValue, latLink
| |
| local longitude, lonConv, lonValue, lonLink
| |
| local latDirection, latDirectionN, latDirectionS, latDirectionEN
| |
| local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN
| |
| local degSymbol, minSymbol, secSymbol, separator
| |
| | |
| local latDegrees = nil
| |
| local latMinutes = nil
| |
| local latSeconds = nil
| |
| local lonDegrees = nil
| |
| local lonMinutes = nil
| |
| local lonSeconds = nil
| |
| | |
| local latDegSym = ""
| |
| local latMinSym = ""
| |
| local latSecSym = ""
| |
| local lonDegSym = ""
| |
| local lonMinSym = ""
| |
| local lonSecSym = ""
| |
| | |
| local latDirectionEN_N = "N"
| |
| local latDirectionEN_S = "S"
| |
| local lonDirectionEN_E = "E"
| |
| local lonDirectionEN_W = "W"
| |
| | |
| if not raw then
| |
| latDirectionN = i18n['coord']['latitude-north']
| |
| latDirectionS = i18n['coord']['latitude-south']
| |
| lonDirectionE = i18n['coord']['longitude-east']
| |
| lonDirectionW = i18n['coord']['longitude-west']
| |
| | |
| degSymbol = i18n['coord']['degrees']
| |
| minSymbol = i18n['coord']['minutes']
| |
| secSymbol = i18n['coord']['seconds']
| |
| separator = i18n['coord']['separator']
| |
| else
| |
| latDirectionN = latDirectionEN_N
| |
| latDirectionS = latDirectionEN_S
| |
| lonDirectionE = lonDirectionEN_E
| |
| lonDirectionW = lonDirectionEN_W
| |
| | |
| degSymbol = "/"
| |
| minSymbol = "/"
| |
| secSymbol = "/"
| |
| separator = "/"
| |
| end
| |
| | |
| latitude = datavalue['latitude']
| |
| longitude = datavalue['longitude']
| |
| | |
| if latitude < 0 then
| |
| latDirection = latDirectionS
| |
| latDirectionEN = latDirectionEN_S
| |
| latitude = math.abs(latitude)
| |
| else
| |
| latDirection = latDirectionN
| |
| latDirectionEN = latDirectionEN_N
| |
| end
| |
| | |
| if longitude < 0 then
| |
| lonDirection = lonDirectionW
| |
| lonDirectionEN = lonDirectionEN_W
| |
| longitude = math.abs(longitude)
| |
| else
| |
| lonDirection = lonDirectionE
| |
| lonDirectionEN = lonDirectionEN_E
| |
| end
| |
| | |
| precision = datavalue['precision']
| |
| | |
| if not precision or precision <= 0 then
| |
| precision = 1 / 3600 -- precision not set (correctly), set to arcsecond
| |
| end
| |
| | |
| -- remove insignificant detail
| |
| latitude = math.floor(latitude / precision + 0.5) * precision
| |
| longitude = math.floor(longitude / precision + 0.5) * precision
| |
| | |
| if precision >= 1 - (1 / 60) and precision < 1 then
| |
| precision = 1
| |
| elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then
| |
| precision = 1 / 60
| |
| end
| |
| | |
| if precision >= 1 then
| |
| unitsPerDegree = 1
| |
| elseif precision >= (1 / 60) then
| |
| unitsPerDegree = 60
| |
| else
| |
| unitsPerDegree = 3600
| |
| end
| |
| | |
| numDigits = math.ceil(-math.log10(unitsPerDegree * precision))
| |
| | |
| if numDigits <= 0 then
| |
| numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead
| |
| end
| |
| | |
| strFormat = "%." .. numDigits .. "f"
| |
| | |
| if precision >= 1 then
| |
| latDegrees = strFormat:format(latitude)
| |
| lonDegrees = strFormat:format(longitude)
| |
| | |
| if not raw then
| |
| latDegSym = replaceDecimalMark(latDegrees) .. degSymbol
| |
| lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol
| |
| else
| |
| latDegSym = latDegrees .. degSymbol
| |
| lonDegSym = lonDegrees .. degSymbol
| |
| end
| |
| else
| |
| latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits
| |
| lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits
| |
| | |
| if precision >= (1 / 60) then
| |
| latMinutes = latConv
| |
| lonMinutes = lonConv
| |
| else
| |
| latSeconds = latConv
| |
| lonSeconds = lonConv
| |
| | |
| latMinutes = math.floor(latSeconds / 60)
| |
| lonMinutes = math.floor(lonSeconds / 60)
| |
| | |
| latSeconds = strFormat:format(latSeconds - (latMinutes * 60))
| |
| lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60))
| |
| | |
| if not raw then
| |
| latSecSym = replaceDecimalMark(latSeconds) .. secSymbol
| |
| lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol
| |
| else
| |
| latSecSym = latSeconds .. secSymbol
| |
| lonSecSym = lonSeconds .. secSymbol
| |
| end
| |
| end
| |
| | |
| latDegrees = math.floor(latMinutes / 60)
| |
| lonDegrees = math.floor(lonMinutes / 60)
| |
| | |
| latDegSym = latDegrees .. degSymbol
| |
| lonDegSym = lonDegrees .. degSymbol
| |
| | |
| latMinutes = latMinutes - (latDegrees * 60)
| |
| lonMinutes = lonMinutes - (lonDegrees * 60)
| |
| | |
| if precision >= (1 / 60) then
| |
| latMinutes = strFormat:format(latMinutes)
| |
| lonMinutes = strFormat:format(lonMinutes)
| |
| | |
| if not raw then
| |
| latMinSym = replaceDecimalMark(latMinutes) .. minSymbol
| |
| lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol
| |
| else
| |
| latMinSym = latMinutes .. minSymbol
| |
| lonMinSym = lonMinutes .. minSymbol
| |
| end
| |
| else
| |
| latMinSym = latMinutes .. minSymbol
| |
| lonMinSym = lonMinutes .. minSymbol
| |
| end
| |
| end
| |
| | |
| latValue = latDegSym .. latMinSym .. latSecSym .. latDirection
| |
| lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection
| |
| | |
| value = latValue .. separator .. lonValue
| |
| | |
| if link then
| |
| globe = parseWikidataURL(datavalue['globe'])
| |
| | |
| if globe then
| |
| globe = mw.wikibase.getLabelByLang(globe, "en"):lower()
| |
| else
| |
| globe = "earth"
| |
| end
| |
| | |
| latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_")
| |
| lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_")
| |
| | |
| value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."¶ms="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]"
| |
| end
| |
| | |
| return value
| |
| elseif datatype == 'wikibase-entityid' then
| |
| local label
| |
| local itemID = datavalue['numeric-id']
| |
| | |
| if subtype == 'wikibase-item' then
| |
| itemID = "Q" .. itemID
| |
| elseif subtype == 'wikibase-property' then
| |
| itemID = "P" .. itemID
| |
| else
| |
| return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>'
| |
| end
| |
| | |
| label = self:getLabel(itemID, raw, link, short)
| |
| | |
| if label == "" then
| |
| label = nil
| |
| end
| |
| | |
| return label
| |
| else
| |
| return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>'
| |
| end
| |
| elseif snak.snaktype == 'somevalue' and not noSpecial then
| |
| if raw then
| |
| return " " -- single space represents 'somevalue'
| |
| else
| |
| return i18n['values']['unknown']
| |
| end
| |
| elseif snak.snaktype == 'novalue' and not noSpecial then
| |
| if raw then
| |
| return "" -- empty string represents 'novalue'
| |
| else
| |
| return i18n['values']['none']
| |
| end
| |
| else
| |
| return nil
| |
| end
| |
| end
| |
| | |
| function Config:getSingleRawQualifier(claim, qualifierID)
| |
| local qualifiers
| |
| | |
| if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end
| |
| | |
| if qualifiers and qualifiers[1] then
| |
| return self:getValue(qualifiers[1], true) -- raw = true
| |
| else
| |
| return nil
| |
| end
| |
| end
| |
| | |
| function Config:snakEqualsValue(snak, value)
| |
| local snakValue = self:getValue(snak, true) -- raw = true
| |
| | |
| if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end
| |
| | |
| return snakValue == value
| |
| end
| |
| | |
| function Config:setRank(rank)
| |
| local rankPos
| |
| | |
| if rank == p.flags.best then
| |
| self.bestRank = true
| |
| self.flagBest = true -- mark that 'best' flag was given
| |
| return
| |
| end
| |
| | |
| if rank:sub(1,9) == p.flags.preferred then
| |
| rankPos = 1
| |
| elseif rank:sub(1,6) == p.flags.normal then
| |
| rankPos = 2
| |
| elseif rank:sub(1,10) == p.flags.deprecated then
| |
| rankPos = 3
| |
| else
| |
| return
| |
| end
| |
| | |
| -- one of the rank flags was given, check if another one was given before
| |
| if not self.flagRank then
| |
| self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks
| |
| self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before
| |
| self.flagRank = true -- mark that a rank flag was given
| |
| end
| |
| | |
| if rank:sub(-1) == "+" then
| |
| for i = rankPos, 1, -1 do
| |
| self.ranks[i] = true
| |
| end
| |
| elseif rank:sub(-1) == "-" then
| |
| for i = rankPos, #self.ranks do
| |
| self.ranks[i] = true
| |
| end
| |
| else
| |
| self.ranks[rankPos] = true
| |
| end
| |
| end
| |
| | |
| function Config:setPeriod(period)
| |
| local periodPos
| |
| | |
| if period == p.flags.future then
| |
| periodPos = 1
| |
| elseif period == p.flags.current then
| |
| periodPos = 2
| |
| elseif period == p.flags.former then
| |
| periodPos = 3
| |
| else
| |
| return
| |
| end
| |
| | |
| -- one of the period flags was given, check if another one was given before
| |
| if not self.flagPeriod then
| |
| self.periods = {false, false, false} -- no other period flag given before, so unset periods
| |
| self.flagPeriod = true -- mark that a period flag was given
| |
| end
| |
| | |
| self.periods[periodPos] = true
| |
| end
| |
| | |
| function Config:qualifierMatches(claim, id, value)
| |
| local qualifiers
| |
| | |
| if claim.qualifiers then qualifiers = claim.qualifiers[id] end
| |
| if qualifiers then
| |
| for _, v in pairs(qualifiers) do
| |
| if self:snakEqualsValue(v, value) then
| |
| return true
| |
| end
| |
| end
| |
| elseif value == "" then
| |
| -- if the qualifier is not present then treat it the same as the special value 'novalue'
| |
| return true
| |
| end
| |
| | |
| return false
| |
| end
| |
| | |
| function Config:rankMatches(rankPos)
| |
| if self.bestRank then
| |
| return (self.ranks[rankPos] and self.foundRank >= rankPos)
| |
| else
| |
| return self.ranks[rankPos]
| |
| end
| |
| end
| |
| | |
| function Config:timeMatches(claim)
| |
| local startTime = nil
| |
| local startTimeY = nil
| |
| local startTimeM = nil
| |
| local startTimeD = nil
| |
| local endTime = nil
| |
| local endTimeY = nil
| |
| local endTimeM = nil
| |
| local endTimeD = nil
| |
| | |
| if self.periods[1] and self.periods[2] and self.periods[3] then
| |
| -- any time
| |
| return true
| |
| end
| |
| | |
| startTime = self:getSingleRawQualifier(claim, aliasesP.startTime)
| |
| if startTime and startTime ~= "" and startTime ~= " " then
| |
| startTimeY, startTimeM, startTimeD = parseDate(startTime)
| |
| end
| |
| | |
| endTime = self:getSingleRawQualifier(claim, aliasesP.endTime)
| |
| if endTime and endTime ~= "" and endTime ~= " " then
| |
| endTimeY, endTimeM, endTimeD = parseDate(endTime)
| |
| end
| |
| | |
| if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then
| |
| -- invalidate end time if it precedes start time
| |
| endTimeY = nil
| |
| endTimeM = nil
| |
| endTimeD = nil
| |
| end
| |
| | |
| if self.periods[1] then
| |
| -- future
| |
| if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then
| |
| return true
| |
| end
| |
| end
| |
| | |
| if self.periods[2] then
| |
| -- current
| |
| if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and
| |
| (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then
| |
| return true
| |
| end
| |
| end
| |
| | |
| if self.periods[3] then
| |
| -- former
| |
| if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then
| |
| return true
| |
| end
| |
| end
| |
| | |
| return false
| |
| end
| |
| | |
| function Config:processFlag(flag)
| |
| if not flag then
| |
| return false
| |
| end
| |
| | |
| if flag == p.flags.linked then
| |
| self.curState.linked = true
| |
| return true
| |
| elseif flag == p.flags.raw then
| |
| self.curState.rawValue = true
| |
| | |
| if self.curState == self.states[parameters.reference] then
| |
| -- raw reference values end with periods and require a separator (other than none)
| |
| self.separators["sep%r"][1] = {" "}
| |
| end
| |
| | |
| return true
| |
| elseif flag == p.flags.short then
| |
| self.curState.shortName = true
| |
| return true
| |
| elseif flag == p.flags.multilanguage then
| |
| self.curState.anyLanguage = true
| |
| return true
| |
| elseif flag == p.flags.unit then
| |
| self.curState.unitOnly = true
| |
| return true
| |
| elseif flag == p.flags.mdy then
| |
| self.mdyDate = true
| |
| return true
| |
| elseif flag == p.flags.single then
| |
| self.singleClaim = true
| |
| return true
| |
| elseif flag == p.flags.sourced then
| |
| self.sourcedOnly = true
| |
| return true
| |
| elseif flag == p.flags.edit then
| |
| self.editable = true
| |
| return true
| |
| elseif flag == p.flags.editAtEnd then
| |
| self.editable = true
| |
| self.editAtEnd = true
| |
| return true
| |
| elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then
| |
| self:setRank(flag)
| |
| return true
| |
| elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then
| |
| self:setPeriod(flag)
| |
| return true
| |
| elseif flag == "" then
| |
| -- ignore empty flags and carry on
| |
| return true
| |
| else
| |
| return false
| |
| end
| |
| end
| |
| | |
| function Config:processFlagOrCommand(flag)
| |
| local param = ""
| |
| | |
| if not flag then
| |
| return false
| |
| end
| |
| | |
| if flag == p.claimCommands.property or flag == p.claimCommands.properties then
| |
| param = parameters.property
| |
| elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then
| |
| self.states.qualifiersCount = self.states.qualifiersCount + 1
| |
| param = parameters.qualifier .. self.states.qualifiersCount
| |
| self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])}
| |
| elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then
| |
| param = parameters.reference
| |
| else
| |
| return self:processFlag(flag)
| |
| end
| |
| | |
| if self.states[param] then
| |
| return false
| |
| end
| |
| | |
| -- create a new state for each command
| |
| self.states[param] = State:new(self, param)
| |
| | |
| -- use "%x" as the general parameter name
| |
| self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p"
| |
| | |
| -- set the separator
| |
| self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately
| |
| | |
| if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then
| |
| self.states[param].singleValue = true
| |
| end
| |
| | |
| self.curState = self.states[param]
| |
| | |
| return true
| |
| end
| |
| | |
| function Config:processSeparators(args)
| |
| local sep
| |
| | |
| for i, v in pairs(self.separators) do
| |
| if args[i] then
| |
| sep = replaceSpecialChars(args[i])
| |
| | |
| if sep ~= "" then
| |
| self.separators[i][1] = {sep}
| |
| else
| |
| self.separators[i][1] = nil
| |
| end
| |
| end
| |
| end
| |
| end
| |
| | |
| function Config:setFormatAndSeparators(state, parsedFormat)
| |
| state.parsedFormat = parsedFormat
| |
| state.separator = self.separators["sep"]
| |
| state.movSeparator = self.separators["sep"..parameters.separator]
| |
| state.puncMark = self.separators["punc"]
| |
| end
| |
| | |
| -- determines if a claim has references by prefetching them from the claim using getReferences,
| |
| -- which applies some filtering that determines if a reference is actually returned,
| |
| -- and caches the references for later use
| |
| function State:isSourced(claim)
| |
| self.conf.prefetchedRefs = self:getReferences(claim)
| |
| return (#self.conf.prefetchedRefs > 0)
| |
| end
| |
| | |
| function State:resetCaches()
| |
| -- any prefetched references of the previous claim must not be used
| |
| self.conf.prefetchedRefs = nil
| |
| end
| |
| | |
| function State:claimMatches(claim)
| |
| local matches, rankPos
| |
| | |
| -- first of all, reset any cached values used for the previous claim
| |
| self:resetCaches()
| |
| | |
| -- if a property value was given, check if it matches the claim's property value
| |
| if self.conf.propertyValue then
| |
| matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue)
| |
| else
| |
| matches = true
| |
| end
| |
| | |
| -- if any qualifier values were given, check if each matches one of the claim's qualifier values
| |
| for i, v in pairs(self.conf.qualifierIDsAndValues) do
| |
| matches = (matches and self.conf:qualifierMatches(claim, i, v))
| |
| end
| |
| | |
| -- check if the claim's rank and time period match
| |
| rankPos = rankTable[claim.rank] or 4
| |
| matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim))
| |
| | |
| -- if only claims with references must be returned, check if this one has any
| |
| if self.conf.sourcedOnly then
| |
| matches = (matches and self:isSourced(claim)) -- prefetches and caches references
| |
| end
| |
| | |
| return matches, rankPos
| |
| end
| |
| | |
| function State:out()
| |
| local result -- collection of arrays with value objects
| |
| local valuesArray -- array with value objects
| |
| local sep = nil -- value object
| |
| local out = {} -- array with value objects
| |
| | |
| local function walk(formatTable, result)
| |
| local valuesArray = {} -- array with value objects
| |
| | |
| for i, v in pairs(formatTable.req) do
| |
| if not result[i] or not result[i][1] then
| |
| -- we've got no result for a parameter that is required on this level,
| |
| -- so skip this level (and its children) by returning an empty result
| |
| return {}
| |
| end
| |
| end
| |
| | |
| for _, v in ipairs(formatTable) do
| |
| if v.param then
| |
| valuesArray = mergeArrays(valuesArray, result[v.str])
| |
| elseif v.str ~= "" then
| |
| valuesArray[#valuesArray + 1] = {v.str}
| |
| end
| |
| | |
| if v.child then
| |
| valuesArray = mergeArrays(valuesArray, walk(v.child, result))
| |
| end
| |
| end
| |
| | |
| return valuesArray
| |
| end
| |
| | |
| -- iterate through the results from back to front, so that we know when to add separators
| |
| for i = #self.results, 1, -1 do
| |
| result = self.results[i]
| |
| | |
| -- if there is already some output, then add the separators
| |
| if #out > 0 then
| |
| sep = self.separator[1] -- fixed separator
| |
| result[parameters.separator] = {self.movSeparator[1]} -- movable separator
| |
| else
| |
| sep = nil
| |
| result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark
| |
| end
| |
| | |
| valuesArray = walk(self.parsedFormat, result)
| |
| | |
| if #valuesArray > 0 then
| |
| if sep then
| |
| valuesArray[#valuesArray + 1] = sep
| |
| end
| |
| | |
| out = mergeArrays(valuesArray, out)
| |
| end
| |
| end
| |
| | |
| -- reset state before next iteration
| |
| self.results = {}
| |
| | |
| return out
| |
| end
| |
| | |
| -- level 1 hook
| |
| function State:getProperty(claim)
| |
| local value = {self:getValue(claim.mainsnak)} -- create one value object
| |
| | |
| if #value > 0 then
| |
| return {value} -- wrap the value object in an array and return it
| |
| else
| |
| return {} -- return empty array if there was no value
| |
| end
| |
| end
| |
| | |
| -- level 1 hook
| |
| function State:getQualifiers(claim, param)
| |
| local qualifiers
| |
| | |
| if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end
| |
| if qualifiers then
| |
| -- iterate through claim's qualifier statements to collect their values;
| |
| -- return array with multiple value objects
| |
| return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook
| |
| else
| |
| return {} -- return empty array
| |
| end
| |
| end
| |
| | |
| -- level 2 hook
| |
| function State:getQualifier(snak)
| |
| local value = {self:getValue(snak)} -- create one value object
| |
| | |
| if #value > 0 then
| |
| return {value} -- wrap the value object in an array and return it
| |
| else
| |
| return {} -- return empty array if there was no value
| |
| end
| |
| end
| |
| | |
| -- level 1 hook
| |
| function State:getAllQualifiers(claim, param, result, hooks)
| |
| local out = {} -- array with value objects
| |
| local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object
| |
| | |
| -- iterate through the output of the separate "qualifier(s)" commands
| |
| for i = 1, self.conf.states.qualifiersCount do
| |
| | |
| -- if a hook has not been called yet, call it now
| |
| if not result[parameters.qualifier..i] then
| |
| self:callHook(parameters.qualifier..i, hooks, claim, result)
| |
| end
| |
| | |
| -- if there is output for this particular "qualifier(s)" command, then add it
| |
| if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then
| |
| | |
| -- if there is already some output, then add the separator
| |
| if #out > 0 and sep then
| |
| out[#out + 1] = sep
| |
| end
| |
| | |
| out = mergeArrays(out, result[parameters.qualifier..i])
| |
| end
| |
| end
| |
| | |
| return out
| |
| end
| |
| | |
| -- level 1 hook
| |
| function State:getReferences(claim)
| |
| if self.conf.prefetchedRefs then
| |
| -- return references that have been prefetched by isSourced
| |
| return self.conf.prefetchedRefs
| |
| end
| |
| | |
| if claim.references then
| |
| -- iterate through claim's reference statements to collect their values;
| |
| -- return array with multiple value objects
| |
| return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook
| |
| else
| |
| return {} -- return empty array
| |
| end
| |
| end
| |
| | |
| -- level 2 hook
| |
| function State:getReference(statement)
| |
| local citeParamMapping = i18n['cite']['param-mapping']
| |
| local citeConfig = i18n['cite']['config']
| |
| local citeTypes = i18n['cite']['output-types']
| |
|
| |
| -- will hold rendered properties of the reference which are not directly from statement.snaks,
| |
| -- Namely, is URL generated from an external ID.
| |
| local additionalProcessedProperties = {}
| |
| -- for each citation type, there will be an associative array that associates lists of rendered properties
| |
| -- to citation-template parameters
| |
| local candidateParams = {}
| |
| -- like above, but only associates one rendered property to each parameter; if the above variable
| |
| -- contains more strings for a parameter, the strings will be assigned to numbered params (e.g. "author1")
| |
| local citeParams = {}
| |
| | |
| local citeErrors = {}
| |
| local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved
| |
| | |
| local version = 12 -- increment this each time the below logic is changed to avoid conflict errors
| |
| | |
| if not statement.snaks then
| |
| return {}
| |
| end
| |
| | |
| -- don't use bot-added references referencing Wikimedia projects or containing "inferred from" (such references are not usable on Wikipedia)
| |
| if statement.snaks[aliasesP.importedFrom] or statement.snaks[aliasesP.wikimediaImportURL] or statement.snaks[aliasesP.inferredFrom] then
| |
| return {}
| |
| end
| |
|
| |
| -- don't include "type of reference"
| |
| if statement.snaks[aliasesP.typeOfReference] then
| |
| statement.snaks[aliasesP.typeOfReference] = nil
| |
| end
| |
| | |
| -- don't include "image" to prevent littering
| |
| if statement.snaks[aliasesP.image] then
| |
| statement.snaks[aliasesP.image] = nil
| |
| end
| |
| | |
| -- don't include "language" if it is equal to the local one
| |
| if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then
| |
| statement.snaks[aliasesP.language] = nil
| |
| end
| |
|
| |
| if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then
| |
| -- "stated in" was given but "reference URL" was not.
| |
| -- get "Wikidata property" properties from the item in "stated in"
| |
| -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference
| |
|
| |
| -- find the "Wikidata property" properties in the item from "stated in"
| |
| local wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true)
| |
| for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do
| |
| if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then
| |
| local tempLink = self:getReferenceDetail(statement.snaks, wikidataPropertyOfSource, false, true) -- not raw, linked
| |
| if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL in square brackets.
| |
| -- the link is in wiki markup, so strip the square brackets and the display text
| |
| -- gsub also returns another, discarted value, therefore the result is assigned to tempLink first
| |
| tempLink = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1")
| |
| additionalProcessedProperties[aliasesP.referenceURL] = {tempLink}
| |
| statement.snaks[wikidataPropertyOfSource] = nil
| |
| break
| |
| end
| |
| end
| |
| end
| |
| end
| |
|
| |
| -- initialize candidateParams and citeParams
| |
| for _, citeType in ipairs(citeTypes) do
| |
| candidateParams[citeType] = {}
| |
| citeParams[citeType] = {}
| |
| end
| |
| | |
| -- fill candidateParams
| |
| for _, citeType in ipairs(citeTypes) do
| |
| -- This will contain value--priority pairs for each param name.
| |
| local candidateValuesAndPriorities = {}
| |
| | |
| -- fill candidateValuesAndPriorities
| |
| for refProperty in pairs(statement.snaks) do
| |
| if citeErrors[citeType] then
| |
| break
| |
| end
| |
| repeat -- just a simple wrapper to emulate "continue"
| |
| -- set mappingKey and prefix
| |
| local mappingKey
| |
| local prefix = ""
| |
| if statement.snaks[refProperty][1].datatype == 'external-id' then
| |
| mappingKey = "external-id"
| |
| prefix = self.conf:getLabel(refProperty)
| |
|
| |
| if prefix ~= "" then
| |
| prefix = prefix .. " "
| |
| end
| |
| else
| |
| mappingKey = refProperty
| |
| end
| |
|
| |
| local paramName = citeParamMapping[citeType][mappingKey]
| |
| -- skip properties with empty parameter name
| |
| if paramName == "" then
| |
| break -- skip this property for this value of citeType
| |
| end
| |
|
| |
| -- handle unknown properties in the reference
| |
| if not paramName then
| |
| referenceEmpty = false
| |
| local error_message = errorText("unknown-property-in-ref", refProperty)
| |
| assert(error_message) -- Should not be nil
| |
| citeErrors[citeType] = error_message
| |
| break
| |
| end
| |
|
| |
| -- set processedProperty
| |
| local processedProperty
| |
| local raw = false -- if the value is wanted raw
| |
| if isValueInTable(paramName, citeConfig[citeType]["raw-value-params"] or {}) then
| |
| raw = true
| |
| end
| |
| if isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then
| |
| -- Multiple values may be given.
| |
| processedProperty = self:getReferenceDetails(statement.snaks, refProperty, raw, self.linked, true) -- anyLang = true
| |
| else
| |
| -- If multiple values are given, all but the first suitable one are discarted.
| |
| processedProperty = {self:getReferenceDetail(statement.snaks, refProperty, raw, self.linked and (statement.snaks[refProperty][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true
| |
| end
| |
|
| |
| if #processedProperty == 0 then
| |
| break
| |
| end
| |
|
| |
| referenceEmpty = false
| |
|
| |
| -- add an empty entry to candidateValuesAndPriorities, if there isn't one already
| |
| if not candidateValuesAndPriorities[paramName] then
| |
| candidateValuesAndPriorities[paramName] = {}
| |
| end
| |
|
| |
| -- find the priority of refProperty
| |
| local thisPropertyPriority = -1
| |
| local thisParamPrioritization = citeConfig[citeType]["prioritization"][paramName]
| |
| if thisParamPrioritization then
| |
| for i_priority, i_property in ipairs(thisParamPrioritization) do
| |
| if i_property == refProperty then
| |
| thisPropertyPriority = i_priority
| |
| end
| |
| end
| |
| end
| |
|
| |
| for _, propertyValue in pairs(processedProperty) do
| |
| table.insert(
| |
| candidateValuesAndPriorities[paramName],
| |
| {prefix .. propertyValue, thisPropertyPriority}
| |
| )
| |
| end
| |
| until true
| |
| end
| |
|
| |
| -- fill candidateParams[citeType]
| |
| if not citeErrors[citeType] then
| |
| local compareValuePriorities = function(pair1, pair2)
| |
| if pair1[2] == -1 and pair2[2] ~= -1 then
| |
| return false
| |
| end
| |
| if pair1[2] ~= -1 and pair2[2] == -1 then
| |
| return true
| |
| end
| |
| return pair1[2] < pair2[2]
| |
| end
| |
|
| |
| -- fill candidateParams[citeType][paramName] for each used param
| |
| for paramName, _ in pairs(candidateValuesAndPriorities) do
| |
| table.sort(candidateValuesAndPriorities[paramName], compareValuePriorities)
| |
| candidateParams[citeType][paramName] = {}
| |
| for _, valuePriorityPair in ipairs(candidateValuesAndPriorities[paramName]) do
| |
| table.insert(candidateParams[citeType][paramName], valuePriorityPair[1])
| |
| end
| |
| end
| |
| end
| |
| end
| |
|
| |
| -- handle additional properties
| |
| for refProperty in pairs(additionalProcessedProperties) do
| |
| for _, citeType in ipairs(citeTypes) do
| |
| repeat
| |
| -- skip if there already have been errors
| |
| if citeErrors[citeType] then
| |
| break
| |
| end
| |
|
| |
| local paramName = citeParamMapping[citeType][refProperty]
| |
| -- handle unknown properties in the reference
| |
| if not paramName then
| |
| -- Skip this additional property, but do not cause an error.
| |
| break
| |
| end
| |
| if paramName == "" then
| |
| break
| |
| end
| |
|
| |
| referenceEmpty = false
| |
|
| |
| if not candidateParams[citeType][paramName] then
| |
| candidateParams[citeType][paramName] = {}
| |
| end
| |
| for _, propertyValue in pairs(additionalProcessedProperties[refProperty]) do
| |
| table.insert(candidateParams[citeType][paramName], propertyValue)
| |
| end
| |
| until true
| |
| end
| |
| end
| |
|
| |
| -- fill citeParams
| |
| for _, citeType in ipairs(citeTypes) do
| |
| for paramName, paramValues in pairs(candidateParams[citeType]) do
| |
| if #paramValues == 1 or not isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then
| |
| citeParams[citeType][paramName] = paramValues[1]
| |
| else
| |
| -- There is more than one value for this parameter - the values will
| |
| -- go into separate numbered parameters (e.g. "author1", "author2")
| |
| for paramNum, paramValue in pairs(paramValues) do
| |
| citeParams[citeType][paramName .. paramNum] = paramValue
| |
| end
| |
| end
| |
| end
| |
| end
| |
|
| |
| -- handle missing mandatory parameters for the templates
| |
| for _, citeType in ipairs(citeTypes) do
| |
| for _, requiredCiteParam in pairs(citeConfig[citeType]["mandatory-params"] or {}) do
| |
| if not citeParams[citeType][requiredCiteParam] then -- The required param is not present.
| |
| if citeErrors[citeType] then -- Do not override the previous error, if it exists.
| |
| break
| |
| end
| |
| local error_message = errorText("missing-mandatory-param", requiredCiteParam)
| |
| assert(error_message) -- Should not be nil
| |
| citeErrors[citeType] = error_message
| |
| end
| |
| end
| |
| end
| |
|
| |
| local citeTypeToUse = nil
| |
| | |
| -- choose the output template
| |
| for _, citeType in ipairs(citeTypes) do
| |
| if not citeErrors[citeType] then
| |
| citeTypeToUse = citeType
| |
| break
| |
| end
| |
| end
| |
| | |
| -- set refContent
| |
| local refContent = ""
| |
| if citeTypeToUse then
| |
| local templateToUse = citeConfig[citeTypeToUse]["template"]
| |
| local paramsToUse = citeParams[citeTypeToUse]
| |
|
| |
| if not templateToUse or templateToUse == "" then
| |
| throwError("no-such-reference-template", tostring(templateToUse), i18nPath, citeTypeToUse)
| |
| end
| |
|
| |
| -- if this module is being substituted then build a regular template call, otherwise expand the template
| |
| if mw.isSubsting() then
| |
| for i, v in pairs(paramsToUse) do
| |
| refContent = refContent .. "|" .. i .. "=" .. v
| |
| end
| |
| | |
| refContent = "{{" .. templateToUse .. refContent .. "}}"
| |
| else
| |
| xpcall(
| |
| function () refContent = mw.getCurrentFrame():expandTemplate{title=templateToUse, args=paramsToUse} end,
| |
| function () throwError("no-such-reference-template", templateToUse, i18nPath, citeTypeToUse) end
| |
| )
| |
| end
| |
| | |
| -- If the citation couldn't be displayed using any template, but is not empty (barring ignored propeties), throw an error.
| |
| elseif not referenceEmpty then
| |
| refContent = errorText("malformed-reference-header")
| |
| for _, citeType in ipairs(citeTypes) do
| |
| refContent = refContent .. errorText("template-failure-reason", citeConfig[citeType]["template"], citeErrors[citeType])
| |
| end
| |
| refContent = refContent .. errorText("malformed-reference-footer")
| |
| end
| |
| | |
| -- wrap refContent
| |
| local ref = {}
| |
| if refContent ~= "" then
| |
| ref = {refContent}
| |
| | |
| if not self.rawValue then
| |
| -- this should become a <ref> tag, so save the reference's hash for later
| |
| ref.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['version']) + version)
| |
| end
| |
| return {ref}
| |
| else
| |
| return {}
| |
| end
| |
| end
| |
| | |
| -- gets a detail of one particular type for a reference
| |
| function State:getReferenceDetail(snaks, dType, raw, link, anyLang)
| |
| local switchLang = anyLang
| |
| local value = nil
| |
| | |
| if not snaks[dType] then
| |
| return nil
| |
| end
| |
| | |
| -- if anyLang, first try the local language and otherwise any language
| |
| repeat
| |
| for _, v in ipairs(snaks[dType]) do
| |
| value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true
| |
| | |
| if value then
| |
| break
| |
| end
| |
| end
| |
| | |
| if value or not anyLang then
| |
| break
| |
| end
| |
| | |
| switchLang = not switchLang
| |
| until anyLang and switchLang
| |
| | |
| return value
| |
| end
| |
| | |
| -- gets the details of one particular type for a reference
| |
| function State:getReferenceDetails(snaks, dType, raw, link, anyLang)
| |
| local values = {}
| |
| | |
| if not snaks[dType] then
| |
| return {}
| |
| end
| |
| | |
| for _, v in ipairs(snaks[dType]) do
| |
| -- if nil is returned then it will not be added to the table
| |
| values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true
| |
| end
| |
| | |
| return values
| |
| end
| |
| | |
| -- level 1 hook
| |
| function State:getAlias(object)
| |
| local value = object.value
| |
| local title = nil
| |
| | |
| if value and self.linked then
| |
| if self.conf.entityID:sub(1,1) == "Q" then
| |
| title = mw.wikibase.getSitelink(self.conf.entityID)
| |
| elseif self.conf.entityID:sub(1,1) == "P" then
| |
| title = "d:Property:" .. self.conf.entityID
| |
| end
| |
| | |
| if title then
| |
| value = buildWikilink(title, value)
| |
| end
| |
| end
| |
| | |
| value = {value} -- create one value object
| |
| | |
| if #value > 0 then
| |
| return {value} -- wrap the value object in an array and return it
| |
| else
| |
| return {} -- return empty array if there was no value
| |
| end
| |
| end
| |
| | |
| -- level 1 hook
| |
| function State:getBadge(value)
| |
| value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName)
| |
| | |
| if value == "" then
| |
| value = nil
| |
| end
| |
| | |
| value = {value} -- create one value object
| |
| | |
| if #value > 0 then
| |
| return {value} -- wrap the value object in an array and return it
| |
| else
| |
| return {} -- return empty array if there was no value
| |
| end
| |
| end
| |
| | |
| function State:callHook(param, hooks, statement, result)
| |
| -- call a parameter's hook if it has been defined and if it has not been called before
| |
| if not result[param] and hooks[param] then
| |
| local valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects
| |
| | |
| -- add to the result
| |
| if #valuesArray > 0 then
| |
| result[param] = valuesArray
| |
| result.count = result.count + 1
| |
| else
| |
| result[param] = {} -- an empty array to indicate that we've tried this hook already
| |
| return true -- miss == true
| |
| end
| |
| end
| |
| | |
| return false
| |
| end
| |
| | |
| -- iterate through claims, claim's qualifiers or claim's references to collect values
| |
| function State:iterate(statements, hooks, matchHook)
| |
| matchHook = matchHook or alwaysTrue
| |
| | |
| local matches = false
| |
| local rankPos = nil
| |
| local result, gotRequired
| |
| | |
| for _, v in ipairs(statements) do
| |
| -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.)
| |
| matches, rankPos = matchHook(self, v)
| |
| | |
| if matches then
| |
| result = {count = 0} -- collection of arrays with value objects
| |
| | |
| local function walk(formatTable)
| |
| local miss
| |
| | |
| for i2, v2 in pairs(formatTable.req) do
| |
| -- call a hook, adding its return value to the result
| |
| miss = self:callHook(i2, hooks, v, result)
| |
| | |
| if miss then
| |
| -- we miss a required value for this level, so return false
| |
| return false
| |
| end
| |
| | |
| if result.count == hooks.count then
| |
| -- we're done if all hooks have been called;
| |
| -- returning at this point breaks the loop
| |
| return true
| |
| end
| |
| end
| |
| | |
| for _, v2 in ipairs(formatTable) do
| |
| if result.count == hooks.count then
| |
| -- we're done if all hooks have been called;
| |
| -- returning at this point prevents further childs from being processed
| |
| return true
| |
| end
| |
| | |
| if v2.child then
| |
| walk(v2.child)
| |
| end
| |
| end
| |
| | |
| return true
| |
| end
| |
| gotRequired = walk(self.parsedFormat)
| |
| | |
| -- only append the result if we got values for all required parameters on the root level
| |
| if gotRequired then
| |
| -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank
| |
| if rankPos and self.conf.foundRank > rankPos then
| |
| self.conf.foundRank = rankPos
| |
| end
| |
| | |
| -- append the result
| |
| self.results[#self.results + 1] = result
| |
| | |
| -- break if we only need a single value
| |
| if self.singleValue then
| |
| break
| |
| end
| |
| end
| |
| end
| |
| end
| |
| | |
| return self:out()
| |
| end
| |
| | |
| local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId)
| |
| local id = nil
| |
| local prop = nil
| |
| | |
| if arg then
| |
| if arg:sub(1,1) == ":" then
| |
| page = arg
| |
| eid = nil
| |
| elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then
| |
| eid = arg
| |
| page = nil
| |
| else
| |
| prop = arg
| |
| end
| |
| end
| |
| | |
| if eid then
| |
| if eid:sub(1,9):lower() == "property:" then
| |
| id = replaceAlias(mw.text.trim(eid:sub(10)))
| |
| | |
| if id:sub(1,1):upper() ~= "P" then
| |
| id = ""
| |
| end
| |
| else
| |
| id = replaceAlias(eid)
| |
| end
| |
| elseif page then
| |
| if page:sub(1,1) == ":" then
| |
| page = mw.text.trim(page:sub(2))
| |
| end
| |
| | |
| id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or ""
| |
| end
| |
| | |
| if not id then
| |
| id = mw.wikibase.getEntityIdForCurrentPage() or ""
| |
| end
| |
| | |
| id = id:upper()
| |
| | |
| if not mw.wikibase.isValidEntityId(id) then
| |
| id = ""
| |
| end
| |
| | |
| return id, prop
| |
| end
| |
| | |
| local function nextArg(args)
| |
| local arg = args[args.pointer]
| |
| | |
| if arg then
| |
| args.pointer = args.pointer + 1
| |
| return mw.text.trim(arg)
| |
| else
| |
| return nil
| |
| end
| |
| end
| |
| | |
| local function claimCommand(args, funcName)
| |
| local cfg = Config:new()
| |
| cfg:processFlagOrCommand(funcName) -- process first command (== function name)
| |
| | |
| local lastArg, parsedFormat, formatParams, claims, value
| |
| local hooks = {count = 0}
| |
| | |
| -- set the date if given;
| |
| -- must come BEFORE processing the flags
| |
| if args[p.args.date] then
| |
| cfg.atDate = {parseDate(args[p.args.date])}
| |
| cfg.periods = {false, true, false} -- change default time constraint to 'current'
| |
| end
| |
| | |
| -- process flags and commands
| |
| repeat
| |
| lastArg = nextArg(args)
| |
| until not cfg:processFlagOrCommand(lastArg)
| |
| | |
| -- get the entity ID from either the positional argument, the eid argument or the page argument
| |
| cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], false, args[p.args.globalSiteId])
| |
| | |
| if cfg.entityID == "" then
| |
| return "" -- we cannot continue without a valid entity ID
| |
| end
| |
| | |
| cfg.entity = mw.wikibase.getEntity(cfg.entityID)
| |
| | |
| if not cfg.propertyID then
| |
| cfg.propertyID = nextArg(args)
| |
| end
| |
| | |
| cfg.propertyID = replaceAlias(cfg.propertyID)
| |
| | |
| if not cfg.entity or not cfg.propertyID then
| |
| return "" -- we cannot continue without an entity or a property ID
| |
| end
| |
| | |
| cfg.propertyID = cfg.propertyID:upper()
| |
| | |
| if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then
| |
| return "" -- there is no use to continue without any claims
| |
| end
| |
| | |
| claims = cfg.entity.claims[cfg.propertyID]
| |
| | |
| if cfg.states.qualifiersCount > 0 then
| |
| -- do further processing if "qualifier(s)" command was given
| |
| | |
| if #args - args.pointer + 1 > cfg.states.qualifiersCount then
| |
| -- claim ID or literal value has been given
| |
| | |
| cfg.propertyValue = nextArg(args)
| |
| end
| |
| | |
| for i = 1, cfg.states.qualifiersCount do
| |
| -- check if given qualifier ID is an alias and add it
| |
| cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper()
| |
| end
| |
| elseif cfg.states[parameters.reference] then
| |
| -- do further processing if "reference(s)" command was given
| |
| | |
| cfg.propertyValue = nextArg(args)
| |
| end
| |
| | |
| -- check for special property value 'somevalue' or 'novalue'
| |
| if cfg.propertyValue then
| |
| cfg.propertyValue = replaceSpecialChars(cfg.propertyValue)
| |
| | |
| if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then
| |
| cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue'
| |
| else
| |
| cfg.propertyValue = mw.text.trim(cfg.propertyValue)
| |
| end
| |
| end
| |
| | |
| -- parse the desired format, or choose an appropriate format
| |
| if args["format"] then
| |
| parsedFormat, formatParams = parseFormat(args["format"])
| |
| elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given
| |
| if cfg.states[parameters.property] then -- "propert(y|ies)" command given
| |
| parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier)
| |
| else
| |
| parsedFormat, formatParams = parseFormat(formats.qualifier)
| |
| end
| |
| elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given
| |
| parsedFormat, formatParams = parseFormat(formats.property)
| |
| else -- "reference(s)" command given
| |
| parsedFormat, formatParams = parseFormat(formats.reference)
| |
| end
| |
| | |
| -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon
| |
| if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then
| |
| cfg.separators["sep"..parameters.separator][1] = {";"}
| |
| end
| |
| | |
| -- if only "reference(s)" has been given, set the default separator to none (except when raw)
| |
| if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0
| |
| and not cfg.states[parameters.reference].rawValue then
| |
| cfg.separators["sep"][1] = nil
| |
| end
| |
| | |
| -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent
| |
| if cfg.states.qualifiersCount == 1 then
| |
| cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"]
| |
| end
| |
| | |
| -- process overridden separator values;
| |
| -- must come AFTER tweaking the default separators
| |
| cfg:processSeparators(args)
| |
| | |
| -- define the hooks that should be called (getProperty, getQualifiers, getReferences);
| |
| -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given
| |
| for i, v in pairs(cfg.states) do
| |
| -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q"
| |
| if formatParams[i] or formatParams[i:sub(1, 2)] then
| |
| hooks[i] = getHookName(i, 1)
| |
| hooks.count = hooks.count + 1
| |
| end
| |
| end
| |
| | |
| -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...);
| |
| -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given
| |
| if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then
| |
| hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1)
| |
| hooks.count = hooks.count + 1
| |
| end
| |
| | |
| -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration;
| |
| -- must come AFTER defining the hooks
| |
| if not cfg.states[parameters.property] then
| |
| cfg.states[parameters.property] = State:new(cfg, parameters.property)
| |
| | |
| -- if the "single" flag has been given then this state should be equivalent to "property" (singular)
| |
| if cfg.singleClaim then
| |
| cfg.states[parameters.property].singleValue = true
| |
| end
| |
| end
| |
| | |
| -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values,
| |
| -- which must exist in order to be able to determine if a claim has any references;
| |
| -- must come AFTER defining the hooks
| |
| if cfg.sourcedOnly and not cfg.states[parameters.reference] then
| |
| cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead
| |
| end
| |
| | |
| -- set the parsed format and the separators (and optional punctuation mark);
| |
| -- must come AFTER creating the additonal states
| |
| cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat)
| |
| | |
| -- process qualifier matching values, analogous to cfg.propertyValue
| |
| for i, v in pairs(args) do
| |
| i = tostring(i)
| |
| | |
| if i:match('^[Pp]%d+$') or aliasesP[i] then
| |
| v = replaceSpecialChars(v)
| |
| | |
| -- check for special qualifier value 'somevalue'
| |
| if v ~= "" and mw.text.trim(v) == "" then
| |
| v = " " -- single space represents 'somevalue'
| |
| end
| |
| | |
| cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v
| |
| end
| |
| end
| |
| | |
| -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated)
| |
| claims = sortOnRank(claims)
| |
| | |
| -- then iterate through the claims to collect values
| |
| value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook
| |
| | |
| -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata
| |
| if cfg.editable and value ~= "" then
| |
| value = value .. cfg:getEditIcon()
| |
| end
| |
| | |
| return value
| |
| end
| |
| | |
| local function generalCommand(args, funcName)
| |
| local cfg = Config:new()
| |
| cfg.curState = State:new(cfg)
| |
| | |
| local lastArg
| |
| local value = nil
| |
| | |
| repeat
| |
| lastArg = nextArg(args)
| |
| until not cfg:processFlag(lastArg)
| |
| | |
| -- get the entity ID from either the positional argument, the eid argument or the page argument
| |
| cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true, args[p.args.globalSiteId])
| |
| | |
| if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then
| |
| return "" -- we cannot continue without an entity
| |
| end
| |
| | |
| -- serve according to the given command
| |
| if funcName == p.generalCommands.label then
| |
| value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName)
| |
| elseif funcName == p.generalCommands.title then
| |
| cfg.inSitelinks = true
| |
| | |
| if cfg.entityID:sub(1,1) == "Q" then
| |
| value = mw.wikibase.getSitelink(cfg.entityID)
| |
| end
| |
| | |
| if cfg.curState.linked and value then
| |
| value = buildWikilink(value)
| |
| end
| |
| elseif funcName == p.generalCommands.description then
| |
| value = mw.wikibase.getDescription(cfg.entityID)
| |
| else
| |
| local parsedFormat, formatParams
| |
| local hooks = {count = 0}
| |
| | |
| cfg.entity = mw.wikibase.getEntity(cfg.entityID)
| |
| | |
| if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then
| |
| cfg.curState.singleValue = true
| |
| end
| |
| | |
| if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then
| |
| if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then
| |
| return "" -- there is no use to continue without any aliasses
| |
| end
| |
| | |
| local aliases = cfg.entity.aliases[cfg.langCode]
| |
| | |
| -- parse the desired format, or parse the default aliases format
| |
| if args["format"] then
| |
| parsedFormat, formatParams = parseFormat(args["format"])
| |
| else
| |
| parsedFormat, formatParams = parseFormat(formats.alias)
| |
| end
| |
| | |
| -- process overridden separator values;
| |
| -- must come AFTER tweaking the default separators
| |
| cfg:processSeparators(args)
| |
| | |
| -- define the hook that should be called (getAlias);
| |
| -- only define the hook if the parameter ("%a") has been given
| |
| if formatParams[parameters.alias] then
| |
| hooks[parameters.alias] = getHookName(parameters.alias, 1)
| |
| hooks.count = hooks.count + 1
| |
| end
| |
| | |
| -- set the parsed format and the separators (and optional punctuation mark)
| |
| cfg:setFormatAndSeparators(cfg.curState, parsedFormat)
| |
| | |
| -- iterate to collect values
| |
| value = cfg:concatValues(cfg.curState:iterate(aliases, hooks))
| |
| elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then
| |
| if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then
| |
| return "" -- there is no use to continue without any badges
| |
| end
| |
| | |
| local badges = cfg.entity.sitelinks[cfg.siteID].badges
| |
| | |
| cfg.inSitelinks = true
| |
| | |
| -- parse the desired format, or parse the default aliases format
| |
| if args["format"] then
| |
| parsedFormat, formatParams = parseFormat(args["format"])
| |
| else
| |
| parsedFormat, formatParams = parseFormat(formats.badge)
| |
| end
| |
| | |
| -- process overridden separator values;
| |
| -- must come AFTER tweaking the default separators
| |
| cfg:processSeparators(args)
| |
| | |
| -- define the hook that should be called (getBadge);
| |
| -- only define the hook if the parameter ("%b") has been given
| |
| if formatParams[parameters.badge] then
| |
| hooks[parameters.badge] = getHookName(parameters.badge, 1)
| |
| hooks.count = hooks.count + 1
| |
| end
| |
| | |
| -- set the parsed format and the separators (and optional punctuation mark)
| |
| cfg:setFormatAndSeparators(cfg.curState, parsedFormat)
| |
| | |
| -- iterate to collect values
| |
| value = cfg:concatValues(cfg.curState:iterate(badges, hooks))
| |
| end
| |
| end
| |
| | |
| value = value or ""
| |
| | |
| if cfg.editable and value ~= "" then
| |
| -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata
| |
| value = value .. cfg:getEditIcon()
| |
| end
| |
| | |
| return value
| |
| end
| |
| | |
| -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args)
| |
| local function establishCommands(commandList, commandFunc)
| |
| for _, commandName in pairs(commandList) do
| |
| local function wikitextWrapper(frame)
| |
| local args = copyTable(frame.args)
| |
| args.pointer = 1
| |
| loadI18n(aliasesP, frame)
| |
| return commandFunc(args, commandName)
| |
| end
| |
| p[commandName] = wikitextWrapper
| |
| | |
| local function luaWrapper(args)
| |
| args = copyTable(args)
| |
| args.pointer = 1
| |
| loadI18n(aliasesP)
| |
| return commandFunc(args, commandName)
| |
| end
| |
| p["_" .. commandName] = luaWrapper
| |
| end
| |
| end
| |
| | |
| establishCommands(p.claimCommands, claimCommand)
| |
| establishCommands(p.generalCommands, generalCommand)
| |
| | |
| -- main function that is supposed to be used by wrapper templates
| |
| function p.main(frame)
| |
| if not mw.wikibase then return nil end
| |
| | |
| local f, args
| |
| | |
| loadI18n(aliasesP, frame)
| |
| | |
| -- get the parent frame to take the arguments that were passed to the wrapper template
| |
| frame = frame:getParent() or frame
| |
| | |
| if not frame.args[1] then
| |
| throwError("no-function-specified")
| |
| end
| |
| | |
| f = mw.text.trim(frame.args[1])
| |
| | |
| if f == "main" then
| |
| throwError("main-called-twice")
| |
| end
| |
| | |
| assert(p["_"..f], errorText('no-such-function', f))
| |
| | |
| -- copy arguments from immutable to mutable table
| |
| args = copyTable(frame.args)
| |
| | |
| -- remove the function name from the list
| |
| table.remove(args, 1)
| |
| | |
| return p["_"..f](args)
| |
| end
| |
| | |
| return p
| |