Template:Hatnote This module is intended to fetch data from Wikidata with or without a link to the connected Wikipedia article and with many other features.
UsageEdit
The general structure of a call to this module is as follows. Note that the basic structure consists of positional commands, flags and arguments, which all have a fixed position.
{{Template:Hashinvoke:sandboxTemplate:0Template:Background colorTemplate:0Template:Background colorTemplate:0Template:Background colorTemplate:0Template:Background color}}
Use different [[#Commands|Template:Background color]] to get different kinds of values from Wikidata. At least one command must be given and multiple commands can be combined into one call as shown above (in any order, more than two is also possible), but this only applies to commands from the claim class; calls containing a command from the general class cannot contain any other command. Each command can be followed by any number of [[#Command flags|Template:Background color]], which are optional and can be used to tweak the output generated by that command.
The commands and their flags may be followed by any number of [[#Configuration flags|Template:Background color]], which are also optional and affect the selection of data and the module's behaviour in general. The call is closed with the [[#Positional arguments|Template:Background color]], which may be required depending on the given command(s). Some named arguments (i.e. name-value pairs) also exist, as well as a set of named flags for advanced usage that can be used to change the way the fetched values are merged together into the output.
This module was designed to provide the basic needs for fetching data from Wikidata, but a lot can be achieved through different combinations of calls. For convenience, such combinations could be wrapped into new templates that serve a specific need. See also the section on common use cases below for some examples of useful "building blocks". Likewise, the functionality of this module can be extended by creating wrapper templates that use the main
command provided by this module (just like Template:T does).
Common use casesEdit
Below follows a list of common use cases. In the future, shortcut commands may be implemented that are equivalent to these calls for convenience.
Call | Use case |
---|---|
{{Template:Hashinvoke:Sandbox/Thayts/Wd|label|raw}}
|
Returns the Q-identifier of the Wikidata item connected to the current page (e.g. "Q55"). |
{{#if:{{Template:Hashinvoke:Sandbox/Thayts/Wd|label|raw}}|...}}
|
Performs a check to determine if the current page has a Wikidata item.
Note that this statement relies on a returned value that is either empty or non-empty and that the |
CommandsEdit
The commands (Template:Background color
, Template:Background color
, ...) determine what kind of values are returned. One call can only contain commands from a single class.
Claim classEdit
The claim class commands can be combined, meaning that multiple commands of different types from this class can be given at one time (see above for usage).
Combine multiple commands into one call to this module, instead of making multiple calls to this module with one command each, to be sure that all the returned pieces of information belong to each other (see also the examples below).
Type | Command | Returns | Basic usage | Description |
---|---|---|---|---|
I | property
|
first match[lower-alpha 1] | {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|P1}}
|
Returns the requested property – or list of properties – from the current item-entity or from a given entity.
This command can be given only once in one call. |
properties
|
all matches | {{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|P1}}
| ||
II | qualifier
|
first match[lower-alpha 1] | {{Template:Hashinvoke:Sandbox/Thayts/Wd|qualifier|P1|P2}}
|
Returns the requested qualifier – or list of qualifiers – from the given property of the current item-entity or of a given entity.
Unlike the other claim class commands, this command can be given multiple times to retrieve different qualifiers in one call. |
qualifiers
|
all matches | {{Template:Hashinvoke:Sandbox/Thayts/Wd|qualifiers|P1|P2}}
| ||
III | reference
|
first match[lower-alpha 1] | {{Template:Hashinvoke:Sandbox/Thayts/Wd|reference|P1}}
|
Returns a reference – or list of references – from the given property of the current item-entity or of a given entity.[lower-alpha 2]
This command can be given only once in one call. |
references
|
all matches | {{Template:Hashinvoke:Sandbox/Thayts/Wd|references|P1}}
| ||
|
General classEdit
The general class commands cannot be combined.
Type | Command | Returns | Basic usage | Description |
---|---|---|---|---|
I | label
|
{{Template:Hashinvoke:Sandbox/Thayts/Wd|label}}
|
Returns the label of the current item-entity or of a given entity if present. | |
II | title
|
{{Template:Hashinvoke:Sandbox/Thayts/Wd|title}}
|
Returns the title of the page connected to the current item-entity or to a given item-entity if such page exists. | |
III | description
|
{{Template:Hashinvoke:Sandbox/Thayts/Wd|description}}
|
Returns the description of the current item-entity or of a given entity if present. | |
IV | alias
|
first match[lower-alpha 1] | {{Template:Hashinvoke:Sandbox/Thayts/Wd|alias}}
|
Returns an alias – or list of aliases – of the current item-entity or of a given entity if present. |
aliases
|
all matches | {{Template:Hashinvoke:Sandbox/Thayts/Wd|aliases}}
| ||
V | badge
|
first match[lower-alpha 1] | {{Template:Hashinvoke:Sandbox/Thayts/Wd|badge}}
|
Returns a badge – or list of badges – for the page connected to the current item-entity or to a given item-entity if such page exists. |
badges
|
all matches | {{Template:Hashinvoke:Sandbox/Thayts/Wd|badges}}
| ||
Main class
The main
command is always used alone and does not take any flags or arguments. It allows parent templates to pass on their parameters in the form of commands to this module.
Command | Basic usage | Description |
---|---|---|
main |
{{#invoke:Sandbox/Thayts/Wd|main}} |
Intended for use by wrapper templates (around this module) to invoke one or more of the above commands, returning their respective output.
The parameters passed to the wrapper template are the ones that will be used by the module, e.g. Therefore, any parameters set by the wrapper template itself will be discarded, e.g. |
FlagsEdit
The following (optional) flags are available which can be used to alter this module's behaviour. They must be given after the (first) Template:Background color and before the Template:Background color. For convenience, empty flags (i.e. ||
) are allowed and will simply be ignored.
Command flagsEdit
These flags (Template:Background color
, Template:Background color
, ...) apply to the command that precedes them directly.
Flag | Description |
---|---|
raw
|
Returns the raw value if applicable.
If this flag is used with item or property datatypes, then this will return the Q-identifier or P-identifier instead of the regular label. For quantity datatypes, this flag will strip off any units of measurement, unless the If this flag is used with time datatypes, then the returned date will be in the format of If it is used with globe coordinate datatypes, then it replaces the various symbols with forward slashes in the returned value (e.g. |
linked
|
Creates a link to the Wikipedia article that is connected to the property or qualifier if it exists. Also links units of measurement that may be appended to values.
If this parameter is omitted, then the plain property or qualifier value will be returned. |
short
|
[EXPENSIVE] Returns the Template:Wpl of any entity returned if they have one attached. If that is not the case, then the default behaviour of returning the entity's label will occur.
|
multilanguage
|
Returns monolingual text values in any available language, not just the current wiki's language. |
unit
|
Returns only the unit of measurement for quantity datatypes. |
Configuration flagsEdit
These flags (Template:Background color
) are general configuration flags and can be given anywhere after the first Template:Background color (but before the Template:Background color).
Flag | Description | Command class | |
---|---|---|---|
Combination of: | preferred
|
Sets a rank constraint for the selected claim(s).
The first three set the ranks for which claim(s) will be selected. They can optionally be followed by a If the The default is Output is always sorted from highest rank to lowest (regardless of any of these flags being set). |
claim |
normal
| |||
deprecated
| |||
best
| |||
Combination of: | future
|
Sets a time constraint for the selected claim(s). Uses the claims' qualifiers of Template:Wpl and Template:Wpl to determine if the claim is valid for the selected time period(s).
The default is |
claim |
current
| |||
former
| |||
mdy
|
Returns date values in month-day-year order instead of day-month-year order. | claim | |
single
|
Returns only a single claim instead of multiple (if multiple claims match). Has no effect if the property /properties command is given, in which case this flag would be redundant.
|
claim | |
sourced
|
Only returns claims that have at least one valid reference. | claim | |
One of: | edit
|
Adds a clickable icon after the output that may be used by readers to edit the returned claim on Wikidata.
If |
claim, general |
edit@end
|
ArgumentsEdit
The arguments determine the sources from which all the returned values are fetched.
Positional argumentsEdit
The following table shows the available positional arguments (Template:Background color
) in their fixed order. For each command, the applicable set of arguments is marked. If multiple commands are given, then the applicable set is the union of the individual sets. For instance, if the commands properties
and qualifiers
have been given, then at least both the arguments property_id
and qualifier_id
should be given as well.
More than one qualifier
/qualifiers
command can be given. The order in which these commands with their flags are given matches the order in which the respective qualifier_id
arguments are given.
(required) | (optional) | (optional) | (required) | (optional) | (required) | (required) | |||
{{Template:Hashinvoke:sandbox
|
commands
|
flags
|
entity_id
|
property_id
|
raw_value
|
qualifier_id
|
qualifier_id
|
}}
| |
---|---|---|---|---|---|---|---|---|---|
label , title ,description ,alias /aliases ,badge /badges
|
|||||||||
property /properties
|
|||||||||
reference /references
|
|||||||||
qualifier /qualifiers
|
|||||||||
qualifier /qualifiers (optional 2nd, 3rd, etc.)
| |||||||||
Below follows a description of all positional arguments.
Argument | Description |
---|---|
entity_id
(optional) |
[EXPENSIVE] Q-identifier of the item-entity to be accessed (e.g. Q55 ), P-identifier (or an available alias) of the property-entity to be accessed preceded by the Property: prefix (e.g. Property:P38 ), or page title of the Wikipedia article whose connected item-entity is to be accessed preceded by : , a prefixed colon (e.g. :Netherlands ).
In case of the general class commands, the If this parameter is omitted, then the item-entity connected to the current page will be used (except when |
property_id
|
P-identifier (or an available alias) of the property within the entity to be accessed, without the Property: prefix (e.g. P35 ).
|
raw_value
(optional) |
Either the Q-identifier equal to the property value (e.g. Q29574 ) or a literal value (i.e. string or quantity etc., no entity label) equal to the raw property value of the particular claim to be accessed.
Dates as literal values must be formatted Globe coordinates as literal values must be formatted with forward slashes (i.e. The special type 'no value' can be given by entering the empty string (i.e. To get a literal vertical bar If this parameter is omitted, then all claims (matching any other constraints) within the property will be accessed. |
qualifier_id
|
P-identifier (or an available alias) of the qualifier within the entity to be accessed, without the Property: prefix (e.g. P580 ).
|
Named argumentsEdit
Below follows a description of all named arguments, which are name-value pairs (i.e. |name=value
). These are all optional and can be given anywhere after the first command.
Argument | Description | Command class |
---|---|---|
eid=
|
[EXPENSIVE] This argument can be used to give the Q-identifier (e.g. |eid=Q55 ) or P-identifier (or an available alias) of the entity to be accessed. It offers the same functionality as the positional argument entity_id , with one difference: if the argument is given but its value is left empty (i.e. |eid= ), then no entity is accessed at all instead of the item-entity connected to the current page. This is useful in some cases where a variable entity-ID is expected, but where the item-entity connected to the current page should not be accessed as the default.
Also, the This argument only has effect if the positional argument |
claim, general |
page=
|
[EXPENSIVE] This argument can be used to give the page title (e.g. |page=Netherlands ) of the Wikipedia article whose connected item-entity is to be accessed. It behaves similar to the named argument eid= and can be used instead of the positional argument entity_id (note that no prefixed colon, : , is required). If the argument is given but its value is left empty (i.e. |page= ), then no entity is accessed at all instead of the item-entity connected to the current page.
This argument only has effect if the positional argument |
claim, general |
date=
|
This argument can be used to set a particular date (e.g. |date=1731-02-11 ) relative to which claim matching using the future , current and former flags is done, instead of relative to today. It overrides the default of these flags to current so that by default only claims that were valid at the given date are returned (based on the claims' qualifiers of Template:Wpl and Template:Wpl).
The date value must be formatted |
claim |
<qualifier>=
|
The <qualifier> is a placeholder for a set of arguments that determine which claims should be accessed based on qualifier value, analogous to the pair of positional arguments property_id and raw_value (that determine access based on property value).
As such, Example: Multiple arguments of this type can be given to match multiple qualifier values simultaneously for each claim. |
claim |
Property aliasesEdit
Property aliases are other names for P-identifiers that can be used instead. The following property aliases (which are case-sensitive) are currently available:
Alias | translates to |
P-identifier |
---|---|---|
coord | → | P625 |
image | → | P18 |
author | → | P50 |
publisher | → | P123 |
importedFrom | → | P143 |
statedIn | → | P248 |
pages | → | P304 |
language | → | P407 |
publicationDate | → | P577 |
startTime | → | P580 |
endTime | → | P582 |
chapter | → | P792 |
retrieved | → | P813 |
referenceURL | → | P854 |
sectionVerseOrParagraph | → | P958 |
archiveURL | → | P1065 |
title | → | P1476 |
formatterURL | → | P1630 |
quote | → | P1683 |
shortName | → | P1813 |
archiveDate | → | P2960 |
inferredFrom | → | P3452 |
typeOfReference | → | P3865 |
column | → | P3903 |
Advanced usageEdit
The layout of the output from (a combination of) commands that have both a singular and a plural form (e.g. property
/properties
) can be customized by using a number of named flags, which are name-value pairs (i.e. |flag=value
), that can be given anywhere after the first command. The table below shows the available named flags.
To insert a space at the beginning or end of a value
, use an underscore _
. To get a literal underscore, escape it by placing a backslash \
directly in front of it (i.e. \_
); the same holds for a literal backslash (i.e. \\
). To get a literal vertical bar |
, use {{!}}
or |
.
Named flag | Default value | Default condition | Description | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
format=
|
%p[%s][%r]
|
if the property /properties command was given and the qualifier /qualifiers command was not given
|
The format of a single claim. The available parameters are as follows.
Optional parameters can be given by encapsulating them between square brackets: To use two opening square brackets that directly follow each other (i.e. At least one parameter must be given that is not optional, while the To get a literal | ||||||||||||||||
%q[%s][%r]
|
if the property /properties command was not given and the qualifier /qualifiers command was given
| ||||||||||||||||||
%r
|
if only the reference /references command was given
| ||||||||||||||||||
%p[ <span style="font-size:85\%">(%q)</span>][%s][%r]
|
if the property /properties command was given and the qualifier /qualifiers command was given
| ||||||||||||||||||
%a[%s]
|
if the alias /aliases command was given
| ||||||||||||||||||
%b[%s]
|
if the badge /badges command was given
| ||||||||||||||||||
sep=
|
Template:Dfn
|
default | The fixed separator between each pair of claims, aliases or badges. | ||||||||||||||||
Template:Dfn | if only the reference /references command was given without the raw flag
| ||||||||||||||||||
sep%s=
|
,
|
default | The movable separator between each pair of claims, aliases or badges. This will be the value of the %s parameter applied to all claims, aliases or badges, except for the last in the list (which can be set with the punc flag).
| ||||||||||||||||
;
|
if the property /properties command was not given and the qualifier /qualifiers command was given
| ||||||||||||||||||
sep%q1= , sep%q2= , sep%q3= , ...
|
Template:Dfn
|
default | The separator between each pair of qualifiers of a single claim. These are the value separators for the %q1 , %q2 , %q3 , ... parameters.
If only one | ||||||||||||||||
sep%q=
|
Template:Dfn
|
if exactly one qualifier /qualifiers command was given
|
The separator between each set of qualifiers of a single claim. This is the value separator for the %q parameter.
If only one | ||||||||||||||||
Template:Dfn
|
if more than one qualifier /qualifiers command was given
| ||||||||||||||||||
sep%r=
|
Template:Dfn | default | The separator between each pair of references of a single claim. This is the value separator for the %r parameter.
| ||||||||||||||||
Template:Dfn
|
if the raw flag was given for the reference /references command
| ||||||||||||||||||
punc=
|
Template:Dfn | default | A punctuation mark placed at the end of the output. This will be placed on the %s parameter applied to the last claim (or alias or badge) in the list.
This allows the last claim's references to be placed after the punctuation mark when the output is used as part of a sentence. |
ExamplesEdit
Parameters and output types | Example | Description |
---|---|---|
Q55 = "Netherlands", P395 = "licence plate code"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|Q55|P395}}
|
Gets a literal string value. |
P395 = "licence plate code"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|P395}}
|
If the module is transcluded on the page (which is linked to Q55), then the Q55 can be omitted.
|
Q55 = "Netherlands", P395 = "licence plate code"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|eid=Q55|P395}}
|
An entity-ID can also be given using the eid= argument.
|
P395 = "licence plate code"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|page=|P395}}
|
A page title can be given instead of an entity-ID using the page= argument.
|
Q55 = "Netherlands", P395 = "licence plate code"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|edit|Q55|P395}} |
Adds a clickable icon that may be used to edit the returned value on Wikidata. |
Q55 = "Netherlands", P395 = "licence plate code"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|edit@end|Q55|P395}} |
Places the edit icon at the end of the line. |
Q55 = "Netherlands", P1082 = "population"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|normal+|Q55|P1082}}
|
Gets a single property value from claims with a 'normal' rank or higher. |
Q55 = "Netherlands", P1082 = "population"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|normal+|Q55|P1082}}
|
Gets multiple property values from claims with a 'normal' rank or higher. |
Q55 = "Netherlands", P1082 = "population", P585 = "point in time"
[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifier|normal+|Q55|P1082|P585}}
|
Gets a single qualifier value for each claim, additional to the property value. |
Q55 = "Netherlands", P1082 = "population", P585 = "point in time"
[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifier|references|normal+|Q55|P1082|P585}} |
Gets references for each claim. |
Q55 = "Netherlands", P1082 = "population"
[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]] |
A total of {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|references|Q55|P1082}} people live in the Netherlands.
|
Gets a property with its references. |
Q55 = "Netherlands", P1082 = "population"
[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]] |
The Netherlands has a population of {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|references|Q55|P1082|punc=.}}
|
Adds a punctuation mark at the end of the output, in front of the references. |
Q55 = "Netherlands", P1082 = "population", P585 = "point in time"
[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]] |
<ul>{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifier|references|normal+|Q55|P1082|P585|format=<li>%p[%r][<ul><li>%q</li></ul>]</li>}}</ul>
|
Returns the output in a custom format. |
Q55 = "Netherlands", P1082 = "population", P585 = "point in time"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|qualifier|normal+|Q55|P1082|P585}}
|
Gets a single qualifier per claim, by default for multiple matching claims. |
Q55 = "Netherlands", P1082 = "population", P585 = "point in time"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|qualifier|normal+|single|Q55|P1082|P585}}
|
To get a single qualifier for only a single claim, give the single flag too so that only a single claim will be accessed.
|
Q55 = "Netherlands", P1082 = "population", P585 = "point in time"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|qualifier|Q55|P1082|17132854|P585}}
|
Gets a qualifier from claims for which the (raw) property value matches a given literal value. |
Q55 = "Netherlands", P1082 = "population", P585 = "point in time"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|qualifier|mdy|Q55|P1082|17132854|P585}}
|
Gets dates in month-day-year order. |
Q55 = "Netherlands", P1082 = "population", P585 = "point in time"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|qualifier|raw|Q55|P1082|17132854|P585}}
|
Gets a raw date value. |
Q55 = "Netherlands", P1082 = "population"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|references|Q55|P1082|17132854}}
|
Gets the references from a particular claim. |
Q55 = "Netherlands", P1082 = "population"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|references|raw|Q55|P1082|17132854}}
|
Gets references from a particular claim in their raw form. |
Q55 = "Netherlands", P1081 = "Human Development Index"
[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|references|normal+|Q55|P1081}}
|
Gets properties from each claim with any references they have. |
Q55 = "Netherlands", P1081 = "Human Development Index"
[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|references|normal+|sourced|Q55|P1081}}
|
Only gets properties from claims that have at least one reference. |
Q55 = "Netherlands", P2855 = "VAT-rate", P518 = "applies to part"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|qualifier|Q55|P2855|P518}}
|
Gets a single qualifier value (for each matching claim). |
Q55 = "Netherlands", P2855 = "VAT-rate", P518 = "applies to part"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|qualifiers|Q55|P2855|P518}}
|
Gets multiple qualifier values (for each matching claim). |
Q55 = "Netherlands", P2855 = "VAT-rate", P518 = "applies to part"
[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifiers|Q55|P2855|P518}}
|
Gets multiple property values along with multiple qualifier values. |
Q55 = "Netherlands", P2855 = "VAT-rate", P518 = "applies to part"
[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifiers|Q55|P2855|P518|sep=_+_|sep%s=|sep%q=_/_}}
|
Returns the output with custom separators. |
Q55 = "Netherlands", P35 = "head of state", P580 = "start time", P582 = "end time"
[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifier|qualifier|normal+|Q55|P35|P580|P582}}
|
Gets two different qualifier values for each claim. |
Q55 = "Netherlands", P35 = "head of state", P580 = "start time", P582 = "end time"
[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifier|qualifier|normal+|Q55|P35|P580|P582|sep%q=_–_}}
|
Returns the output with a custom separator. |
Q55 = "Netherlands", P35 = "head of state", P580 = "start time", P582 = "end time"
[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifier|qualifier|normal+|Q55|P35|P580|P582|format=%p[ <span style="font-size:85\%">(%q1[ – %q2])</span>][%s][%r]}}
|
Returns the output in a custom format instead of with a custom separator. |
Q55 = "Netherlands", P35 = "head of state", P580 = "start time", P582 = "end time"
[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifier|qualifier|normal+|Q55|P35|P580|P582|format=%p[ <span style="font-size:85\%">([<![]--%q2]since [%q2--[]>]%q1[ – %q2])</span>][%s][%r]}}
|
To add text only when a certain value is not present, like adding the word since if there is no end time, wrap it in between two optional blocks containing HTML comment tags and the relevant parameter (this also prevents the text from being added to the page source). |
Q55 = "Netherlands", P35 = "head of state", Q29574 = "", P580 = "start time", P582 = "end time"
[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifier|raw|qualifier|normal+|Q55|P35|Q29574|P580|P582|format=%p[ <span style="font-size:85\%">(%q1[ – %q2])</span>][%s][%r]}}
|
Gets a property with qualifiers from claims for which the property matches a given Q-identifier, with one of the qualifier values in its raw form. |
Q55 = "Netherlands", P38 = "currency", P518 = "applies to part"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifiers|normal+|current|Q55|P38|P518}}
|
Gets claims that are currently valid. |
Q55 = "Netherlands", P38 = "currency", P518 = "applies to part"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|linked|qualifiers|normal+|current|Q55|P38|P518}}
|
Gets claims with linked property values. |
Q55 = "Netherlands", P38 = "currency", P518 = "applies to part"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifiers|linked|normal+|current|Q55|P38|P518}}
|
Gets claims with linked qualifier values. |
Q55 = "Netherlands", P38 = "currency", P518 = "applies to part"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|linked|short|qualifiers|linked|normal+|current|Q55|P38|P518}}
|
Gets claims with linked property and qualifier values, with short property values wherever available. |
Q55 = "Netherlands", P38 = "currency", Q4917 = "United States dollar", P518 = "applies to part"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|qualifiers|normal+|current|Q55|P38|Q4917|P518}}
|
Gets qualifiers from claims for which the (raw) property value matches a given Q-identifier. |
Q55 = "Netherlands", P38 = "currency", P518 = "applies to part", Q27561 = ""
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|normal+|current|Q55|P38|P518=Q27561}}
|
Gets properties from claims for which a (raw) qualifier value matches a given Q-identifier. |
Q55 = "Netherlands", P38 = "currency"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|normal+|former|Q55|P38}}
|
Gets claims that were valid in the past. |
Q55 = "Netherlands", P38 = "currency"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|raw|normal+|former|Q55|P38}}
|
Gets raw property values. |
Q55 = "Netherlands", P38 = "currency"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|raw|linked|normal+|former|Q55|P38}}
|
Gets raw property values that are linked to Wikidata. |
Q55 = "Netherlands", P1549 = ""
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|Q55|P1549}}
|
Gets a monolingual text value in the current wiki's language. |
Q55 = "Netherlands", P1549 = "", P407 = "language of work or name", Q36846 = ""
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|multilanguage|Q55|P1549|P407=Q36846}}
|
Gets a monolingual text value in any available language. |
Q55 = "Netherlands", P2884 = "mains voltage"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|Q55|P2884}}
|
Gets a quantity value with its associated unit of measurement. |
Q55 = "Netherlands", P2884 = "mains voltage"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|linked|Q55|P2884}}
|
Gets a quantity value with a linked unit of measurement. |
Q55 = "Netherlands", P2884 = "mains voltage"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|raw|Q55|P2884}}
|
Gets a raw quantity value. |
Q55 = "Netherlands", P2884 = "mains voltage"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|unit|Q55|P2884}}
|
Gets only the unit of measurement. |
Q55 = "Netherlands", P2884 = "mains voltage"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|unit|raw|Q55|P2884}}
|
Gets the raw unit of measurement. |
Q55 = "Netherlands", P625 = "coordinate location"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|Q55|P625}}
|
Gets a globe coordinate value. |
Q55 = "Netherlands", P625 = "coordinate location"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|linked|Q55|P625}} |
Gets a linked globe coordinate value. |
Q55 = "Netherlands", P625 = "coordinate location"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|raw|Q55|P625}}
|
Gets a raw globe coordinate value. |
Q55 = "Netherlands", P625 = "coordinate location"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|Q55|coord}}
|
A property alias can be used instead of the P-identifier. |
Q55 = "Netherlands", P41 = "flag image"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|linked|Q55|P41}} |
Gets a media file name and links to it on Commons. |
Q55 = "Netherlands", P41 = "flag image"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|raw|Q55|P41|format=\[\[File:%p {{!}} thumb {{!}} left\]\]}}
|
A Commons media file can be included on the page as-is by omitting the linked and raw flags, but by using the raw flag it can be freely formatted.
|
Q55 = "Netherlands", P41 = "flag image"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|raw|date=1700-05-06|Q55|P41|format=\[\[File:%p {{!}} thumb {{!}} left\]\]}}
|
To get the value of a property that was valid at a given time, the date= argument can be used.
|
Q55 = "Netherlands", P41 = "flag image"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|raw|date=1700-05-06|former|Q55|P41|format=\[\[File:%p {{!}} thumb {{!}} left\]\]}}
|
The time constraint flags work relatively to the date value given for the date= argument.
|
Q915684 = "", P2534 = "defining formula"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|Q915684|P2534}}
|
Gets a mathematical expression. |
Q915684 = "", P7235 = "", P9758 = ""
[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]] |
<ul>{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifier|linked|Q915684|P7235|P9758|format=<li>%q[ (%p)]</li>}}</ul>
|
Mathematical expressions can be combined with regular text as usual. |
Q6256 = "country", P3896 = "geoshape"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|linked|Q6256|P3896}} |
Gets a geographic shape data file name and links to it on Commons. |
Q4917 = "United States dollar"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|label|Q4917}}
|
Gets an item's label. |
Q4917 = "United States dollar"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|label|short|linked|Q4917}}
|
Gets an item's short and linked label. |
P38 = "currency"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|label|P38}}
|
Gets a property's label. |
P38 = "currency"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|label|linked|P38}}
|
Gets a property's label that is linked to Wikidata. |
Q776 = "Utrecht"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|label|Q776}}
|
Gets an item's label. |
Q776 = "Utrecht"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|label|linked|Q776}}
|
Gets an item's linked label. |
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|label}}
|
If the module is transcluded on the page (which is linked to Q776), then the Q776 can be omitted.
|
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|label|raw}}
|
If just the label command with the raw flag is given, then the Q-identifier of the item connected to the current page is returned.
|
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|label|raw|linked}}
|
If additionally the linked flag is given, then the Q-identifier of the item connected to the current page is linked to Wikidata.
|
Q776 = "Utrecht"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|title|Q776}}
|
Gets the title of the page on the current wiki that is linked to the given item. |
Q776 = "Utrecht"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|title|linked|Q776}}
|
Gets the linked title of the page on the current wiki that is linked to the given item. |
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|title}}
|
If the module is transcluded on the page (which is linked to Q776), then the Q776 can be omitted.
|
Q55 = "Netherlands"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|description|Q55}}
|
Gets an item's description. |
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|description}}
|
If the module is transcluded on the page (which is linked to Q55), then the Q55 can be omitted.
|
Q55 = "Netherlands"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|alias|Q55}}
|
Gets one of an item's aliases. |
Q55 = "Netherlands"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|aliases|Q55}}
|
Gets all of an item's aliases. |
Q55 = "Netherlands"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|alias|linked|Q55}}
|
Gets a linked alias from an item. |
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|alias}}
|
If the module is transcluded on the page (which is linked to Q55), then the Q55 can be omitted.
|
Q2 = "Earth"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|badges|Q2}}
|
Gets the badges for the page on the current wiki that is linked to the given item. |
Q2 = "Earth"
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|badges|raw|Q2}}
|
Gets the raw badges for the page on the current wiki that is linked to the given item. |
[[[:Template:Smallcaps]]] |
{{Template:Hashinvoke:Sandbox/Thayts/Wd|badges}}
|
If the module is transcluded on the page (which is linked to Q2), then the Q2 can be omitted.
|
Example referencesEdit
- ↑ 1.0 1.1 1.2 1.3 1.4 1.5 https://opendata.cbs.nl/statline/#/CBS/nl/dataset/37296ned/table?ts=1560596956049.
- ↑ 2.0 2.1 2.2 https://data.worldbank.org/indicator/SP.POP.TOTL; retrieved: 8 April 2019.
- ↑ 3.0 3.1 http://statline.cbs.nl/StatWeb/publication/?VW=T&DM=SLNL&PA=37296ned&LA=NL; retrieved: 26 August 2014.
- ↑ 4.0 4.1 http://www.cbs.nl/nl-NL/menu/themas/bevolking/publicaties/artikelen/archief/2016/nederland-telt-17-miljoen-inwoners.htm.
- ↑ 5.0 5.1 5.2 5.3 http://statline.cbs.nl/StatWeb/publication/?VW=T&DM=SLNL&PA=37296ned&D1=a&D2=0,10,20,30,40,50,60,(l-1),l&HD=130605-0924&HDR=G1&STB=T; retrieved: 22 August 2015.
- ↑ 6.00 6.01 6.02 6.03 6.04 6.05 6.06 6.07 6.08 6.09 6.10 6.11 6.12 6.13 6.14 6.15 6.16 6.17 6.18 6.19 6.20 6.21 6.22 6.23 6.24 6.25 6.26 6.27 6.28 6.29 6.30 6.31 6.32 6.33 6.34 6.35 6.36 6.37 6.38 6.39 6.40 6.41 6.42 6.43 6.44 6.45 6.46 6.47 6.48 6.49 6.50 6.51 6.52 6.53 6.54 6.55 http://hdr.undp.org/en/data.
- ↑ 7.00 7.01 7.02 7.03 7.04 7.05 7.06 7.07 7.08 7.09 7.10 7.11 7.12 7.13 http://hdr.undp.org/en/countries/profiles/NLD.
-- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]].
local p = {}
local arg = ...
local i18n
--==-- Public declarations and initializations --==--
p.claimCommands = {
property = "property",
properties = "properties",
qualifier = "qualifier",
qualifiers = "qualifiers",
reference = "reference",
references = "references"
}
p.generalCommands = {
label = "label",
title = "title",
description = "description",
alias = "alias",
aliases = "aliases",
badge = "badge",
badges = "badges"
}
p.flags = {
linked = "linked",
short = "short",
raw = "raw",
multilanguage = "multilanguage",
unit = "unit",
number = "number",
-------------
preferred = "preferred",
normal = "normal",
deprecated = "deprecated",
best = "best",
future = "future",
current = "current",
former = "former",
edit = "edit",
editAtEnd = "edit@end",
mdy = "mdy",
single = "single",
sourced = "sourced"
}
p.args = {
eid = "eid",
page = "page",
date = "date",
sort = "sort"
}
--==-- Public constants --==--
-- An Ogham space that, just like a normal space, is not accepted by Wikidata as a valid single-character string value,
-- but which does not get trimmed as leading/trailing whitespace when passed in an invocation's named argument value.
-- This allows it to be used as a special character representing the special value 'somevalue' unambiguously.
-- Another advantage of this character is that it is usually visible as a dash instead of whitespace.
p.SOMEVALUE = " "
p.JULIAN = "Julian"
--==-- Private constants --==--
local NB_SPACE = " "
local ENC_PIPE = "|"
local SLASH = "/"
local LAT_DIR_N_EN = "N"
local LAT_DIR_S_EN = "S"
local LON_DIR_E_EN = "E"
local LON_DIR_W_EN = "W"
local PROP = "prop"
local RANK = "rank"
local CLAIM = "_claim"
local REFERENCE = "_reference"
local UNIT = "_unit"
local UNKNOWN = "_unknown"
--==-- Private declarations and initializations --==--
local aliasesP = {
coord = "P625",
-----------------------
image = "P18",
author = "P50",
publisher = "P123",
importedFrom = "P143",
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"
}
local aliasesQ = {
julianCalendar = "Q11184",
percentage = "Q11229",
commonEra = "Q208141",
prolepticJulianCalendar = "Q1985786",
citeWeb = "Q5637226",
citeQ = "Q22321052"
}
local parameters = {
property = "p",
qualifier = "q",
reference = "r",
alias = "a",
badge = "b",
separator = "s"
}
local formats = {
property = "%p[%s][%r]",
qualifier = "%q[%s][%r]",
reference = "%r",
propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]"
}
local hookNames = { -- {level_1, level_2}
[parameters.property] = {"getProperty"},
[parameters.reference] = {"getReferences", "getReference"},
[parameters.qualifier] = {"getAllQualifiers"},
[parameters.qualifier.."0"] = {"getQualifiers", "getQualifier"},
[parameters.alias] = {"getAlias"},
[parameters.badge] = {"getBadge"},
[parameters.separator] = {"getSeparator"}
}
local defaultSeparators = {
["sep"] = " ",
["sep%s"] = ",",
["sep%q"] = "; ",
["sep%q0"] = ", ",
["sep%r"] = "", -- none
["punc"] = "" -- none
}
local rankTable = {
["preferred"] = {1},
["normal"] = {2},
["deprecated"] = {3}
}
--==-- Private functions --==--
-- 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
-- used to make frame.args mutable, to replace #frame.args (which is always 0)
-- with the actual amount and to simply copy tables;
-- does a shallow copy, so nested tables are not copied but linked
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
-- implementation of pairs that skips numeric keys
local function npairs(t)
return function(t, k)
local v
repeat
k, v = next(t, k)
until k == nil or type(k) ~= 'number'
return k, v
end, t , nil
end
local function toString(object, insideRef, refs)
local mt, value
insideRef = insideRef or false
refs = refs or {{}}
if not object then
refs.squashed = false
return ""
end
mt = getmetatable(object)
if mt.sep then
local array = {}
for _, obj in ipairs(object) do
local ref = refs[1]
if not insideRef and array[1] and mt.sep[1] ~= "" then
refs[1] = {}
end
value = toString(obj, insideRef, refs)
if value ~= "" or (refs.squashed and not array[1]) then
array[#array + 1] = value
else
refs[1] = ref
end
end
value = table.concat(array, mt.sep[1])
else
if mt.hash then
if refs[1][mt.hash] then
refs.squashed = true
return ""
end
insideRef = true
end
if mt.format then
local ref, squashed, array
local function processFormat(format)
local array = {}
local params = {}
-- see if there are required parameters to expand
if format.req then
-- before expanding any parameters, check that none of them is nil
for i, _ in pairs(format.req) do
if not object[i] then
return array -- empty
end
end
end
-- process the format and childs (+1 is needed to process trailing childs)
for i = 1, #format + 1 do
if format.childs and format.childs[i] then
for _, child in ipairs(format.childs[i]) do
local ref = copyTable(refs[1])
local squashed = refs.squashed
local childArray = processFormat(child)
if not childArray[1] then
refs[1] = ref
refs.squashed = squashed
else
mergeArrays(array, childArray)
end
end
end
if format.params and format.params[i] then
array[#array + 1] = toString(object[format[i]], insideRef, refs)
if array[#array] == "" and not refs.squashed then
return {}
end
elseif format[i] then
array[#array + 1] = format[i]
if not insideRef then
refs[1] = {}
end
end
end
return array
end
ref = copyTable(refs[1])
squashed = refs.squashed
array = processFormat(mt.format)
if not array[1] then
refs[1] = ref
refs.squashed = squashed
end
value = table.concat(array)
else
if mt.expand then
local args = {}
for i, j in npairs(object) do
args[i] = toString(j, insideRef)
end
value = mw.getCurrentFrame():expandTemplate{title=mt.expand, args=args}
elseif object.label then
value = object.label
else
value = table.concat(object)
end
if not insideRef and not mt.hash and value ~= "" then
refs[1] = {}
end
end
if mt.sub then
for i, j in pairs(mt.sub) do
value = mw.ustring.gsub(value, i, j)
end
end
if value ~= "" and mt.tag then
value = mw.getCurrentFrame():extensionTag(mt.tag[1], value, mt.tag[2])
if mt.hash then
refs[1][mt.hash] = true
end
end
refs.squashed = false
end
if mt.trail then
value = value .. mt.trail
if not insideRef then
refs[1] = {}
refs.squashed = false
end
end
return value
end
local function loadI18n(aliasesP, frame)
local title
if frame then
-- 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 = arg
end
if not i18n then
i18n = require(title .. "/i18n").init(aliasesP)
end
end
local function replaceAlias(id)
if aliasesP[id] then
id = aliasesP[id]
end
return id
end
local function errorText(code, param)
local text = i18n["errors"][code]
if param then text = mw.ustring.gsub(text, "$1", param) end
return text
end
local function throwError(errorMessage, param)
error(errorText(errorMessage, param))
end
local function replaceDecimalMark(num)
return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1)
end
local function padZeros(num, numDigits)
local numZeros
local negative = false
if num < 0 then
negative = true
num = num * -1
end
num = tostring(num)
numZeros = numDigits - num:len()
for _ = 1, numZeros do
num = "0"..num
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 isPropertyID(id)
return id:match('^P%d+$')
end
local function buildLink(target, label)
local mt = {__tostring=toString}
if not label then
mt.format = {"[", target, "]"}
return setmetatable({target, target=target, isWebTarget=true}, mt), mt
else
mt.format = {"[", target, " ", label, "]"}
return setmetatable({label, target=target, isWebTarget=true}, mt), mt
end
end
local function buildWikilink(target, label)
local mt = {__tostring=toString}
if not label or target == label then
mt.format = {"[[", target, "]]"}
return setmetatable({target, target=target}, mt), mt
else
mt.format = {"[[", target, "|", label, "]]"}
return setmetatable({label, target=target}, mt), mt
end
end
-- does a shallow copy of both the object and the metatable's format,
-- so nested tables are not copied but linked
local function copyValue(vIn)
local vOut = copyTable(vIn)
local mtIn = getmetatable(vIn)
local mtOut = {format=copyTable(mtIn.format), __tostring=toString}
return setmetatable(vOut, mtOut)
end
local function split(str, del, from)
local i, j
from = from or 1
i, j = str:find(del, from)
if i and j then
return str:sub(1, i - 1), str:sub(j + 1), i, j
end
return str
end
local function urlEncode(url)
local i, j, urlSplit, urlPath
local urlPre = ""
local count = 0
local pathEnc = {}
local delim = ""
i, j = url:find("//", 1, true)
-- check if a hostname is present
if i == 1 or (i and url:sub(i - 1, i - 1) == ':') then
urlSplit = {split(url, "[/?#]", j + 1)}
urlPre = urlSplit[1]
-- split the path from the hostname
if urlSplit[2] then
urlPath = url:sub(urlSplit[3], urlSplit[4]) .. urlSplit[2]
else
urlPath = ""
end
else
urlPath = url -- no hostname is present, so it's a path
end
-- encode each part of the path
for part in mw.text.gsplit(urlPath, "[;/?:@&=+$,#]") do
pathEnc[#pathEnc + 1] = delim
pathEnc[#pathEnc + 1] = mw.uri.encode(mw.uri.decode(part, "PATH"), "PATH")
count = count + #part + 1
delim = urlPath:sub(count, count)
end
-- return the properly encoded URL
return urlPre .. table.concat(pathEnc)
end
local function parseWikidataURL(url)
local id
if url:match('^http[s]?://') then
id = ({split(url, "Q")})[2]
if id then
return "Q" .. id
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(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (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(dateA, dateB)
if not dateA[1] or not dateB[1] then
return nil
end
dateA[2] = dateA[2] or 1
dateA[3] = dateA[3] or 1
dateB[2] = dateB[2] or 1
dateB[3] = dateB[3] or 1
if dateA[1] < dateB[1] then
return true
end
if dateA[1] > dateB[1] then
return false
end
if dateA[2] < dateB[2] then
return true
end
if dateA[2] > dateB[2] then
return false
end
if dateA[3] < dateB[3] then
return true
end
return false
end
local function newOptionalHook(hooks)
return function(state, claim)
state:callHooks(hooks, claim)
return true
end
end
local function newPersistHook(params)
return function(state, claim)
local param0
if not state.resultsByStatement[claim][1] then
local mt = copyTable(state.metaTable)
mt.rank = claim.rank
state.resultsByStatement[claim][1] = setmetatable({}, mt)
local rankPos = (rankTable[claim.rank] or {})[1]
if rankPos and rankPos < state.conf.foundRank then
state.conf.foundRank = rankPos
end
end
for param, _ in pairs(params) do
if not state.resultsByStatement[claim][1][param] then
state.resultsByStatement[claim][1][param] = state.resultsByStatement[claim][param] -- persist result
-- if we need to persist "q", then also persist "q1", "q2", etc.
if param == parameters.qualifier then
for i = 1, state.conf.qualifiersCount do
param0 = param..i
if state.resultsByStatement[claim][param0][1] then
state.resultsByStatement[claim][1][param0] = state.resultsByStatement[claim][param0]
end
end
end
end
end
return true
end
end
local function parseFormat(state, formatStr, i)
local iNext, childHooks, param0
local esc = false
local param = 0
local str = ""
local hooks = {}
local optionalHooks = {}
local parsedFormat = {}
local params = {}
local childs = {}
local req = {}
i = i or 1
local function flush()
if str ~= "" then
parsedFormat[#parsedFormat + 1] = str
if param > 0 then
req[str] = true
params[#parsedFormat] = true
if not state.hooksByParam[str] then
if state.conf.statesByParam[str] or str == parameters.separator then
state:newValueHook(str)
elseif str == parameters.qualifier and state.conf.statesByParam[str.."1"] then
state:newValueHook(str)
for i = 1, state.conf.qualifiersCount do
param0 = str..i
if not state.hooksByParam[param0] then
state:newValueHook(param0)
end
end
end
end
hooks[#hooks + 1] = state.hooksByParam[str]
end
str = ""
end
param = 0
end
while i <= #formatStr do
chr = formatStr:sub(i,i)
if not esc then
if chr == '\\' then
if param > 0 then
flush()
end
esc = true
elseif chr == '%' then
flush()
param = 2
elseif chr == '[' then
flush()
iNext = #parsedFormat + 1
if not childs[iNext] then
childs[iNext] = {}
end
childs[iNext][#childs[iNext] + 1], childHooks, i = parseFormat(state, formatStr, i + 1)
if childHooks[1] then
optionalHooks[#optionalHooks + 1] = newOptionalHook(childHooks)
end
elseif chr == ']' then
break
else
if param > 1 then
param = param - 1
elseif param == 1 and not chr:match('%d') then
flush()
end
str = str .. replaceSpecialChar(chr)
end
else
str = str .. chr
esc = false
end
i = i + 1
end
flush()
if hooks[1] then
hooks[#hooks + 1] = newPersistHook(req)
end
mergeArrays(hooks, optionalHooks)
parsedFormat.params = params
parsedFormat.childs = childs
parsedFormat.req = req
return parsedFormat, hooks, i
end
-- this function must stay in sync with the getValue function
local function parseValue(value, datatype)
if datatype == 'quantity' then
return {tonumber(value)}
elseif datatype == 'time' then
local tail
local dateValue = {}
dateValue.len = 4 -- length used for comparing
value, tail = split(value, SLASH)
if tail and tail:lower() == p.JULIAN:lower() then
dateValue[4] = p.JULIAN
end
if value:sub(1,1) == "-" then
dateValue[1], value = split(value, "-", 2)
else
dateValue[1], value = split(value, "-")
end
dateValue[1] = tonumber(dateValue[1])
if value then
dateValue[2], value = split(value, "-")
dateValue[2] = tonumber(dateValue[2])
if value then
dateValue[3] = tonumber(value)
end
end
return dateValue
elseif datatype == 'globecoordinate' then
local part, partsIndex
local coordValue = {}
coordValue.len = 6 -- length used for comparing
for i = 1, 4 do
part, value = split(value, SLASH)
coordValue[i] = tonumber(part)
if not coordValue[i] or not value or i == 4 then
coordValue[i] = nil
partsIndex = i - 1
break
end
end
if part:upper() == LAT_DIR_S_EN then
for i = 1, partsIndex do
coordValue[i] = -coordValue[i]
end
end
if value then
partsIndex = partsIndex + 3
for i = 4, partsIndex do
part, value = split(value, SLASH)
coordValue[i] = tonumber(part)
if not coordValue[i] or not value then
partsIndex = i - 1
break
end
end
if value and value:upper() == LON_DIR_W_EN then
for i = 4, partsIndex do
coordValue[i] = -coordValue[i]
end
end
end
return coordValue
elseif datatype == 'wikibase-entityid' then
return {value:sub(1,1):upper(), tonumber(value:sub(2))}
end
return {value}
end
local function getEntityId(arg, eid, page, allowOmitPropPrefix)
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) 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
--==-- Classes --==--
local Config = {}
-- allows for recursive calls
function Config:new()
local cfg = setmetatable({}, self)
self.__index = self
cfg.separators = {
["sep"] = {defaultSeparators["sep"]},
["sep%q"] = {defaultSeparators["sep%q"]},
["sep%r"] = {defaultSeparators["sep%r"]},
["sep%s"] = setmetatable({defaultSeparators["sep%s"]}, {__tostring=toString}),
["punc"] = setmetatable({defaultSeparators["punc"]}, {__tostring=toString})
}
cfg.entity = nil
cfg.entityID = nil
cfg.propertyID = nil
cfg.propertyValue = nil
cfg.qualifierIDs = {}
cfg.qualifierIDsAndValues = {}
cfg.qualifiersCount = 0
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.filterBeforeRank = 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.curTime = os.time()
cfg.mdyDate = false
cfg.singleClaim = false
cfg.sourcedOnly = false
cfg.editable = false
cfg.editAtEnd = false
cfg.inSitelinks = false
cfg.emptyAllowed = 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.movSeparator = cfg.separators["sep%s"]
cfg.puncMark = cfg.separators["punc"]
cfg.statesByParam = {}
cfg.statesByID = {}
cfg.curState = nil
cfg.sortKeys = {}
return cfg
end
local State = {}
function State:new(cfg, level, param, id)
local stt = setmetatable({}, self)
self.__index = self
stt.conf = cfg
stt.level = level
stt.param = param
stt.linked = false
stt.rawValue = false
stt.shortName = false
stt.anyLanguage = false
stt.freeUnit = false
stt.freeNumber = false
stt.maxResults = 0 -- 0 means unlimited
stt.metaTable = nil
stt.results = {}
stt.resultsByStatement = {}
stt.references = {}
stt.hooksByParam = {}
stt.hooksByID = {}
stt.valHooksByIdOrParam = {}
stt.valHooks = {}
stt.sortable = {}
stt.sortPaths = {}
stt.propState = nil
if level and level > 1 then
stt.hooks = {stt:newValueHook(param), stt.addToResults}
stt.separator = cfg.separators["sep%"..param] or cfg.separators["sep"] -- fall back to "sep" for getAlias and getBadge
stt.resultsDatatype = nil
else
stt.hooks = {}
stt.separator = cfg.separators["sep"]
stt.resultsDatatype = {CLAIM}
end
if id then
cfg:addToStatesByID(stt, id)
elseif param then
cfg.statesByParam[param] = stt
end
return stt
end
function Config:addToStatesByID(state, id)
if not self.statesByID[id] then
self.statesByID[id] = {}
end
self.statesByID[id][#self.statesByID[id] + 1] = state
end
-- if id == nil then item connected to current page is used
function Config:getLabel(id, raw, link, short, emptyAllowed)
local label
local mt = {__tostring=toString}
local value = setmetatable({}, mt)
if not id then
id = mw.wikibase.getEntityIdForCurrentPage()
if not id then
return value, mt -- empty value
end
end
id = id:upper() -- just to be sure
-- check if given id actually exists
if not mw.wikibase.isValidEntityId(id) or not mw.wikibase.entityExists(id) then
return value, mt -- empty value
end
if raw then
label = id
else
-- try short name first if requested
if short then
label = p.property{aliasesP.shortName, [p.args.eid] = id, format = "%"..parameters.property} -- get short name
if label == "" then
label = nil
end
end
-- get label
if not label then
label = mw.wikibase.getLabelByLang(id, self.langCode)
end
if not label and not emptyAllowed then
return value, mt -- empty value
end
value.label = label or ""
end
-- split id for potential numeric sorting
value[1] = id:sub(1,1)
value[2] = tonumber(id:sub(2))
-- build a link if requested
if link then
if raw or value[1] == "P" then
-- link to Wikidata if raw or if property (which has no sitelink)
value.target = id
if value[1] == "P" then
value.target = "Property:" .. value.target
end
value.target = "d:" .. value.target
else
-- else, value[1] == "Q"
value.target = mw.wikibase.getSitelink(id)
end
if value.target and label then
mt.format = ({buildWikilink(value.target, label)})[2].format
end
end
return value, mt
end
function Config:getEditIcon()
local value = ""
local prefix = ""
local front = NB_SPACE
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
function Config:convertUnit(unit, raw, link, short)
local itemID
local mt = {__tostring=toString}
local value = setmetatable({}, mt)
if unit == "" or unit == "1" then
return value, mt
end
itemID = parseWikidataURL(unit)
if itemID then
if itemID == aliasesQ.percentage then
value[1] = itemID:sub(1,1)
value[2] = itemID:sub(2)
if not raw then
value.label = "%"
elseif link then
value.target = "d:" .. itemID
mt.format = ({buildWikilink(value.target, itemID)})[2].format
end
else
value, mt = self:getLabel(itemID, raw, link, short)
if value.label then
value.unitSep = NB_SPACE
end
end
end
return value, mt
end
function State:getValue(snak)
return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.freeUnit, self.freeNumber, false, self.conf.emptyAllowed, self.param:sub(1, 1))
end
-- returns a value object in the general form {raw_component_1, raw_component_2, ...} with metatable {format={str_component_1, str_component_2, ...}};
-- 'format' is the string representation of the value in unconcatenated form to exploit Lua's string internalization to reduce memory usage;
-- this function must stay in sync with the parseValue function
function Config:getValue(snak, raw, link, short, anyLang, freeUnit, freeNumber, noSpecial, emptyAllowed, param)
local mt = {__tostring=toString}
local value = setmetatable({}, mt)
if snak.snaktype == 'value' then
local datatype = snak.datavalue.type
local subtype = snak.datatype
local datavalue = snak.datavalue.value
mt.datatype = {datatype}
if datatype == 'string' then
local datatypes = {datatype, subtype}
value[1] = datavalue
mt.datatype = datatypes
if subtype == 'url' and link then
-- create link explicitly
if raw then
-- will render as a linked number like [1]
value, mt = buildLink(datavalue)
else
value, mt = buildLink(datavalue, datavalue)
end
mt.datatype = datatypes
return value
elseif subtype == 'commonsMedia' then
if link then
value, mt = buildWikilink("c:File:" .. datavalue, datavalue)
mt.datatype = datatypes
elseif not raw then
mt.format = {"[[", "File:", datavalue, "]]"}
end
return value
elseif subtype == 'geo-shape' and link then
value, mt = buildWikilink("c:" .. datavalue, datavalue)
mt.datatype = datatypes
return value
elseif subtype == 'math' and not raw then
local attribute = nil
if (param == parameters.property or (param == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then
attribute = {qid = self.entityID}
end
mt.tag = {"math", attribute}
return value
elseif subtype == 'musical-notation' and not raw then
mt.tag = {"score"}
return value
elseif subtype == 'external-id' and link then
local url = p.property{aliasesP.formatterURL, [p.args.eid] = snak.property, format = "%"..parameters.property} -- get formatter URL
if url ~= "" then
url = urlEncode(mw.ustring.gsub(url, "$1", datavalue))
value, mt = buildLink(url, datavalue)
mt.datatype = datatypes
end
return value
else
return value
end
elseif datatype == 'monolingualtext' then
if anyLang or datavalue['language'] == self.langCode then
value[1] = datavalue['text']
value.language = datavalue['language']
end
return value
elseif datatype == 'quantity' then
local valueStr, unit
if freeNumber or not freeUnit then
-- get value and strip + signs from front
valueStr = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1")
value[1] = tonumber(valueStr)
-- assertion; we should always have a value
if not value[1] then
return value
end
if not raw then
-- replace decimal mark based on locale
valueStr = replaceDecimalMark(valueStr)
-- add delimiters for readability
valueStr = i18n.addDelimiters(valueStr)
mt.format = {valueStr}
end
end
if freeUnit or (not freeNumber and not raw) then
local mtUnit
unit, mtUnit = self:convertUnit(datavalue['unit'], raw, link, short)
if freeUnit and not freeNumber then
value = unit
mt = mtUnit
mt.datatype = {UNIT}
elseif unit[1] then
value[#value + 1] = unit[1]
value[#value + 1] = unit[2]
value.len = 1 -- (max) length used for sorting
value.target = unit.target
value.unitLabel = unit.label
value.unitSep = unit.unitSep
if raw then
mt.format = {valueStr, SLASH}
mergeArrays(mt.format, mtUnit.format or unit)
else
mt.format[#mt.format + 1] = unit.unitSep -- may be nil
mergeArrays(mt.format, mtUnit.format or {unit.label})
end
end
end
return value
elseif datatype == 'time' then
local y, m, d, p, yDiv, yRound, yFull, yRaw, mStr, ce, calendarID, target
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 = math.abs(y)
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)
-- take the first year of the century/millennium as the raw year
-- (e.g. 1901 for 20th century or 2001 for 3rd millennium)
yRaw = (yRound - 1) * yFactor + 1
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
yRound = yRaw
end
else
-- precision == 8
-- round decades down (e.g. 2010s)
yRound = math.floor(yDiv) * yFactor
yRaw = yRound
if not raw then
prefix = i18n['datetime']['prefixes']['decade-period']
suffix = i18n['datetime']['suffixes']['decade-period']
end
end
if 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)
yRaw = yRaw + yFactor - 1
if raw then
yRound = yRaw
end
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
yRaw = yRound * yFactor
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 = yRaw
if yRound == 1 then
suffix = i18n['datetime']['suffixes']['year']
else
suffix = i18n['datetime']['suffixes']['years']
end
end
else
yRound = yRaw
end
end
else
yRound = y
yRaw = yRound
mayAddCalendar = true
end
value[1] = yRaw * sign
value[2] = m
value[3] = d
value.len = 3 -- (max) length used for sorting
value.precision = precision
mt.format = {}
if not raw then
if prefix ~= "" then
mt.format[1] = prefix
end
if m then
mStr = self.langObj:formatDate("F", "1-"..m.."-1")
if d then
if self.mdyDate then
mt.format[#mt.format + 1] = mStr
mt.format[#mt.format + 1] = " "
mt.format[#mt.format + 1] = tostring(d)
mt.format[#mt.format + 1] = ","
else
mt.format[#mt.format + 1] = tostring(d)
mt.format[#mt.format + 1] = " "
mt.format[#mt.format + 1] = mStr
end
else
mt.format[#mt.format + 1] = mStr
end
mt.format[#mt.format + 1] = " "
end
mt.format[#mt.format + 1] = tostring(yRound)
if suffix ~= "" then
mt.format[#mt.format + 1] = suffix
end
if sign < 0 then
ce = i18n['datetime']['BCE']
elseif precision <= 5 then
ce = i18n['datetime']['CE']
end
if ce then
mt.format[#mt.format + 1] = " "
if link then
target = mw.wikibase.getSitelink(aliasesQ.commonEra)
if target then
mergeArrays(mt.format, ({buildWikilink(target, ce)})[2].format)
else
mt.format[#mt.format + 1] = ce
end
else
mt.format[#mt.format + 1] = ce
end
end
else
mt.format[1] = padZeros(yRound * sign, 4)
if m then
mt.format[#mt.format + 1] = "-"
mt.format[#mt.format + 1] = padZeros(m, 2)
if d then
mt.format[#mt.format + 1] = "-"
mt.format[#mt.format + 1] = padZeros(d, 2)
end
end
end
calendarID = parseWikidataURL(datavalue['calendarmodel'])
if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then
value[4] = p.JULIAN -- as value.len == 3, this will not be taken into account while sorting
if mayAddCalendar then
if not raw then
mt.format[#mt.format + 1] = " ("
if link then
target = mw.wikibase.getSitelink(aliasesQ.julianCalendar)
if target then
mergeArrays(mt.format, ({buildWikilink(target, i18n['datetime']['julian'])})[2].format)
else
mt.format[#mt.format + 1] = i18n['datetime']['julian']
end
else
mt.format[#mt.format + 1] = i18n['datetime']['julian']
end
mt.format[#mt.format + 1] = ")"
else
mt.format[#mt.format + 1] = SLASH
mt.format[#mt.format + 1] = p.JULIAN
end
end
end
return value
elseif datatype == 'globecoordinate' then
-- logic from https://github.com/DataValues/Geo (v4.0.1)
local precision, unitsPerDegree, numDigits, strFormat, globe
local latitude, latConv, latLink
local longitude, lonConv, lonLink
local latDirection, latDirectionN, latDirectionS, latDirectionEN
local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN
local latDegrees, latMinutes, latSeconds
local lonDegrees, lonMinutes, lonSeconds
local degSymbol, minSymbol, secSymbol, separator
local latSign = 1
local lonSign = 1
local latFormat = {}
local lonFormat = {}
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 = LAT_DIR_N_EN
latDirectionS = LAT_DIR_S_EN
lonDirectionE = LON_DIR_E_EN
lonDirectionW = LON_DIR_W_EN
degSymbol = SLASH
minSymbol = SLASH
secSymbol = SLASH
separator = SLASH
end
latitude = datavalue['latitude']
longitude = datavalue['longitude']
if latitude < 0 then
latDirection = latDirectionS
latDirectionEN = LAT_DIR_S_EN
latSign = -1
latitude = math.abs(latitude)
else
latDirection = latDirectionN
latDirectionEN = LAT_DIR_N_EN
end
if longitude < 0 then
lonDirection = lonDirectionW
lonDirectionEN = LON_DIR_W_EN
lonSign = -1
longitude = math.abs(longitude)
else
lonDirection = lonDirectionE
lonDirectionEN = LON_DIR_E_EN
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)
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
latFormat[5] = replaceDecimalMark(latSeconds)
lonFormat[5] = replaceDecimalMark(lonSeconds)
else
latFormat[5] = latSeconds
lonFormat[5] = lonSeconds
end
latFormat[6] = secSymbol
lonFormat[6] = secSymbol
value[3] = tonumber(latSeconds) * latSign
value[6] = tonumber(lonSeconds) * lonSign
end
latDegrees = math.floor(latMinutes / 60)
lonDegrees = math.floor(lonMinutes / 60)
latMinutes = latMinutes - (latDegrees * 60)
lonMinutes = lonMinutes - (lonDegrees * 60)
if precision >= (1 / 60) then
latMinutes = strFormat:format(latMinutes)
lonMinutes = strFormat:format(lonMinutes)
else
latMinutes = tostring(latMinutes)
lonMinutes = tostring(lonMinutes)
end
if not raw then
latFormat[3] = replaceDecimalMark(latMinutes)
lonFormat[3] = replaceDecimalMark(lonMinutes)
else
latFormat[3] = latMinutes
lonFormat[3] = lonMinutes
end
latFormat[4] = minSymbol
lonFormat[4] = minSymbol
value[2] = tonumber(latMinutes) * latSign
value[5] = tonumber(lonMinutes) * lonSign
latDegrees = tostring(latDegrees)
lonDegrees = tostring(lonDegrees)
end
if not raw then
latFormat[1] = replaceDecimalMark(latDegrees)
lonFormat[1] = replaceDecimalMark(lonDegrees)
else
latFormat[1] = latDegrees
lonFormat[1] = lonDegrees
end
latFormat[2] = degSymbol
lonFormat[2] = degSymbol
value[1] = tonumber(latDegrees) * latSign
value[4] = tonumber(lonDegrees) * lonSign
value.len = 6 -- (max) length used for sorting
latFormat[#latFormat + 1] = latDirection
lonFormat[#lonFormat + 1] = lonDirection
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.target = "https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."¶ms="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe
value.isWebTarget = true
mt.format = {"[", value.target, " "}
mergeArrays(mt.format, latFormat)
mt.format[#mt.format + 1] = separator
mergeArrays(mt.format, lonFormat)
mt.format[#mt.format + 1] = "]"
else
mt.format = latFormat
mt.format[#mt.format + 1] = separator
mergeArrays(mt.format, lonFormat)
end
return value
elseif datatype == 'wikibase-entityid' then
local itemID = datavalue['numeric-id']
if subtype == 'wikibase-item' then
itemID = "Q" .. itemID
elseif subtype == 'wikibase-property' then
itemID = "P" .. itemID
else
value[1] = errorText('unknown-data-type', subtype)
mt.datatype = {UNKNOWN}
mt.format = {'<strong class="error">', value[1], '</strong>'}
return value
end
value, mt = self:getLabel(itemID, raw, link, short, emptyAllowed)
mt.datatype = {datatype, subtype}
return value
else
value[1] = errorText('unknown-data-type', datatype)
mt.datatype = {UNKNOWN}
mt.format = {'<strong class="error">', value[1], '</strong>'}
return value
end
elseif snak.snaktype == 'somevalue' then
if not noSpecial then
value[1] = p.SOMEVALUE -- one Ogham space represents 'somevalue'
if not raw then
mt.format = {i18n['values']['unknown']}
end
end
mt.datatype = {snak.snaktype}
return value
elseif snak.snaktype == 'novalue' then
if not noSpecial then
value[1] = "" -- empty string represents 'novalue'
if not raw then
mt.format = {i18n['values']['none']}
end
end
mt.datatype = {snak.snaktype}
return value
else
value[1] = errorText('unknown-data-type', snak.snaktype)
mt.datatype = {UNKNOWN}
mt.format = {'<strong class="error">', value[1], '</strong>'}
return value
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
local mt = getmetatable(snakValue)
if mt.datatype[1] == UNKNOWN then
return false
end
value = parseValue(value, mt.datatype[1])
for i = 1, (value.len or #value) do
if snakValue[i] ~= value[i] then
return false
end
end
return true
end
function Config:setRank(rank)
local rankPos, step, to
if rank == p.flags.best then
self.bestRank = true
self.flagBest = true -- mark that 'best' flag was given
return
end
if rank:match('[+-]$') then
if rank:sub(-1) == "-" then
step = 1
to = #self.ranks
else
step = -1
to = 1
end
rank = rank:sub(1, -2)
end
if rank == p.flags.preferred then
rankPos = 1
elseif rank == p.flags.normal then
rankPos = 2
elseif rank == 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 to then
for i = rankPos, to, step 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 _, qualifier in pairs(qualifiers) do
if self:snakEqualsValue(qualifier, 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)
endTime = self:getSingleRawQualifier(claim, aliasesP.endTime)
if startTime and endTime and datePrecedesDate(endTime, startTime) then
-- invalidate end time if it precedes start time
endTime = nil
end
if self.periods[1] then
-- future
if startTime and datePrecedesDate(self.atDate, startTime) then
return true
end
end
if self.periods[2] then
-- current
if (not startTime or not datePrecedesDate(self.atDate, startTime)) and
(not endTime or datePrecedesDate(self.atDate, endTime)) then
return true
end
end
if self.periods[3] then
-- former
if endTime and not datePrecedesDate(self.atDate, endTime) 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.statesByParam[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.freeUnit = true
return true
elseif flag == p.flags.number then
self.curState.freeNumber = 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
self.filterBeforeRank = 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:processCommand(command, general)
local param, level
if not command then
return false
end
-- prevent general commands from being processed as valid commands if we only expect claim commands
if general then
if command == p.generalCommands.alias or command == p.generalCommands.aliases then
param = parameters.alias
level = 2 -- level 1 hook will be treated as a level 2 hook
elseif command == p.generalCommands.badge or command == p.generalCommands.badges then
param = parameters.badge
level = 2 -- level 1 hook will be treated as a level 2 hook
else
return false
end
elseif command == p.claimCommands.property or command == p.claimCommands.properties then
param = parameters.property
level = 1
elseif command == p.claimCommands.qualifier or command == p.claimCommands.qualifiers then
self.qualifiersCount = self.qualifiersCount + 1
param = parameters.qualifier .. self.qualifiersCount
self.separators["sep%"..param] = {defaultSeparators["sep%q0"]}
level = 2
elseif command == p.claimCommands.reference or command == p.claimCommands.references then
param = parameters.reference
level = 2
else
return nil
end
if self.statesByParam[param] then
return false
end
-- create a new state for each command
self.curState = State:new(self, level, param)
if command == p.claimCommands.property or
command == p.claimCommands.qualifier or
command == p.claimCommands.reference or
command == p.generalCommands.alias or
command == p.generalCommands.badge then
self.curState.maxResults = 1
end
return true
end
function Config:processCommandOrFlag(commandOrFlag)
local success = self:processCommand(commandOrFlag)
if success == nil then
success = self:processFlag(commandOrFlag)
end
return success
end
function Config:processSeparators(args)
for i, v in pairs(self.separators) do
if args[i] then
self.separators[i][1] = replaceSpecialChars(args[i])
end
end
end
function State:isSourced(claim)
return self.hooksByParam[parameters.reference](self, claim)
end
function State:claimMatches(claim)
local matches
-- 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
matches = (matches and self.conf:rankMatches((rankTable[claim.rank] or {})[1]) 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))
end
return matches
end
function State:newSortFunction()
local sortPaths = self.sortPaths
local sortable = self.sortable
local none = {""}
local function resolveValues(sortPath, a, b)
local aVal = nil
local bVal = nil
local sortKey = nil
for _, subPath in ipairs(sortPath) do
local aSub, bSub, key
if #subPath == 0 then
aSub = a
bSub = b
else
if #subPath == 1 then
aSub = subPath[1]
bSub = subPath[1]
if subPath.key then
key = subPath[1]
end
else
aSub, bSub, key = resolveValues(subPath, a, b)
end
sortKey = sortKey or key
end
if not aVal then
aVal = aSub
bVal = bSub
else
aVal = aVal[aSub]
bVal = bVal[bSub]
end
end
return aVal, bVal, sortKey
end
return function(a, b)
for _, sortPath in ipairs(sortPaths) do
local valLen, aPart, bPart
local aValue, bValue, sortKey = resolveValues(sortPath, a, b)
if not sortKey or sortable[sortKey] then
aValue = aValue or none
bValue = bValue or none
if aValue.label or bValue.label then
aValue = {aValue.label or ""}
bValue = {bValue.label or ""}
valLen = 1
else
valLen = aValue.len or #aValue
end
for i = 1, valLen do
aPart = aValue[i]
bPart = bValue[i]
if aPart ~= bPart then
if aPart == nil then
return not sortPath.desc
elseif bPart == nil then
return sortPath.desc
elseif aPart == p.SOMEVALUE or aPart == "" then
if aPart == p.SOMEVALUE and bPart == "" then
return true
end
return false
elseif bPart == p.SOMEVALUE or bPart == "" then
if bPart == p.SOMEVALUE and aPart == "" then
return false
end
return true
end
if sortPath.desc then
return aPart > bPart
else
return aPart < bPart
end
end
end
end
end
return false
end
end
function State:getHookFunction(param)
if param:len() > 1 then
param = param:sub(1, 1).."0"
end
-- fall back to 1 for getAlias and getBadge
return (State[hookNames[param][self.level]] or State[hookNames[param][1]])
end
function State:newValueHook(param, id)
local hook, idOrParam
local func = self:getHookFunction(param)
if self.level > 1 then
idOrParam = 1
else
idOrParam = id or param
end
hook = function(state, statement)
local datatype
if not state.resultsByStatement[statement] then
state.resultsByStatement[statement] = {}
end
if not state.resultsByStatement[statement][idOrParam] then
state.resultsByStatement[statement][idOrParam] = func(state, statement, idOrParam)
if not state.resultsDatatype then
state.resultsDatatype = copyTable(getmetatable(state.resultsByStatement[statement][1]).datatype)
end
end
return (#state.resultsByStatement[statement][idOrParam] > 0)
end
self.hooksByParam[idOrParam] = hook
return hook
end
function State:prepareSortKey(sortKey)
local desc = false
local sortPath = nil
local param = nil
local id = nil
local newID = nil
if sortKey:match('[+-]$') then
if sortKey:sub(-1) == "-" then
desc = true
end
sortKey = sortKey:sub(1, -2)
end
if sortKey == RANK then
return {{rankTable}, {{}, {"rank"}}, desc=desc}
elseif sortKey:sub(1,1) == '%' then
-- param <= param
sortKey = sortKey:sub(2)
param = sortKey
if param == parameters.property then
sortPath = {{self.resultsByStatement}, {}, {param, key=true}, desc=desc}
else
if param == parameters.qualifier then
param = parameters.qualifier.."1"
elseif not param:match('^'..parameters.qualifier..'%d+$') then
return nil
end
sortPath = {{self.resultsByStatement}, {}, {param, key=true}, {1}, desc=desc}
end
if not self.conf.statesByParam[param] then
return nil
end
else
local baseParam, level, state
local index = 0
if sortKey == PROP then
id = sortKey
baseParam = parameters.property
level = 1
sortPath = {{self.resultsByStatement}, {}, {id, key=true}, desc=desc}
else
sortKey = replaceAlias(sortKey):upper()
id = sortKey
if not isPropertyID(id) then
return nil
end
baseParam = parameters.qualifier.."0"
level = 2
sortPath = {{self.resultsByStatement}, {}, {id, key=true}, {1}, desc=desc}
end
if not self.conf.statesByID[id] then
self.conf.statesByID[id] = {}
end
repeat
index = index + 1
if self.conf.statesByID[id][index] then
-- id <= param
state = self.conf.statesByID[id][index]
param = state.param
else
-- id <= id
param = baseParam
newID = id
state = State:new(self.conf, level, param, newID)
state.freeNumber = true
state.maxResults = 1
self.conf.statesByParam[newID] = state
end
until not state.rawValue and not (state.freeUnit and not state.freeNumber)
if id == PROP and index > 1 then
self.propState = state
self.propState.resultsByStatement = self.resultsByStatement
end
end
return sortPath, param, id, newID
end
function State:newValidationHook(param, id, newID)
local invalid = false
local validated = false
local idOrParam = id or param
local newIdOrParam = newID or param
if not self.hooksByParam[newIdOrParam] then
self:newValueHook(param, newID)
end
local hook = self.hooksByParam[newIdOrParam]
local function validationHook(state, claim)
if invalid then
return false
end
if hook(state.propState or state, claim) and not validated then
local datatype
validated = true
datatype = getmetatable(state.resultsByStatement[claim][newIdOrParam]).datatype[1]
if datatype == UNKNOWN then
invalid = true
return false
end
state.sortable[idOrParam] = true
end
state.resultsByStatement[claim][idOrParam] = state.resultsByStatement[claim][newIdOrParam]
return true
end
self.valHooksByIdOrParam[idOrParam] = validationHook
self.valHooks[#self.valHooks + 1] = validationHook
return validationHook
end
function State:parseFormat(formatStr)
local parsedFormat, hooks = parseFormat(self, formatStr)
-- make sure that at least one required parameter has been defined
if not next(parsedFormat.req) then
throwError("missing-required-parameter")
end
-- make sure that the separator parameter "%s" is not amongst the required parameters
if parsedFormat.req[parameters.separator] then
throwError("extra-required-parameter", "%"..parameters.separator)
end
self.metaTable = {
format = parsedFormat,
datatype = {CLAIM},
__tostring = toString
}
return hooks
end
-- level 1 hook
function State:getProperty(claim)
return self:getValue(claim.mainsnak)
end
-- level 1 hook
function State:getQualifiers(claim, param)
local qualifiers
if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param] or param] end
if qualifiers then
-- iterate through claim's qualifier statements to collect their values
self.conf.statesByParam[param]:iterate(qualifiers) -- pass qualifier state
end
-- return array with multiple value objects (or empty array if there were no results)
return self.conf.statesByParam[param]:getAndResetResults()
end
-- level 2 hook
function State:getQualifier(snak)
return self:getValue(snak)
end
-- level 1 hook
function State:getAllQualifiers(claim, param)
local param0
local array = setmetatable({}, {sep=self.conf.separators["sep%"..param], __tostring=toString})
-- iterate through the results of the separate "qualifier(s)" commands
for i = 1, self.conf.qualifiersCount do
param0 = param..i
-- add the result if there is any, calling the hook in the process if it's not been called yet
if self.hooksByParam[param0](self, claim) then
array[#array + 1] = self.resultsByStatement[claim][param0]
end
end
return array
end
-- level 1 hook
function State:getReferences(claim, param)
if claim.references then
-- iterate through claim's reference statements to collect their values
self.conf.statesByParam[param]:iterate(claim.references) -- pass reference state
end
-- return array with multiple value objects (or empty array if there were no results)
return self.conf.statesByParam[param]:getAndResetResults()
end
-- level 2 hook
function State:getReference(statement)
local key, keyNum, citeWeb, citeQ, label, mt2
local mt = {datatype={REFERENCE}, __tostring=toString, __pairs=npairs}
local value = setmetatable({}, mt)
local params = {}
local paramKeys = {}
local skipKeys = {}
local citeValues = {['web'] = {}, ['q'] = {}}
local citeValueKeys = {['web'] = {}, ['q'] = {}}
local citeMismatch = {}
local useCite = nil
local useValues = {}
local useValueKeys = nil
local str = nil
local version = 2 -- increment this each time the below logic is changed to avoid conflict errors
if not statement.snaks then
return value
end
-- if we've parsed the exact same reference before, then return the cached one
-- (note that this means that multiple occurences of the same value object could end up in the results)
if self.references[statement.hash] then
return self.references[statement.hash]
end
self.references[statement.hash] = value
-- don't include "imported from", which is added by a bot
if statement.snaks[aliasesP.importedFrom] then
statement.snaks[aliasesP.importedFrom] = nil
end
-- don't include "inferred from", which is added by a bot
if statement.snaks[aliasesP.inferredFrom] then
statement.snaks[aliasesP.inferredFrom] = nil
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 tostring(self:getReferenceDetail(statement.snaks[aliasesP.language])[1]) == self.conf.langName then
statement.snaks[aliasesP.language] = nil
end
-- retrieve all the other parameters
for i in pairs(statement.snaks) do
-- multiple authors may be given
if i == aliasesP.author then
params[i] = self:getReferenceDetails(statement.snaks[i], false, self.linked, true, " & ") -- link = true/false, anyLang = true
else
params[i] = self:getReferenceDetail(statement.snaks[i], false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true) -- link = true/false, anyLang = true
end
if not params[i][1] then
params[i] = nil
else
paramKeys[#paramKeys + 1] = i
-- add the parameter to each matching type of citation
for j in pairs(citeValues) do
label = ""
-- do so if there was no mismatch with a previous parameter
if not citeMismatch[j] then
if j == 'q' and statement.snaks[i][1].datatype == 'external-id' then
key = 'external-id'
label = tostring(self.conf:getLabel(i))
else
key = i
end
-- check if this parameter is not mismatching itself
if i18n['cite'][j][key] then
key = i18n['cite'][j][key]
-- continue if an option is available in the corresponding cite template
if key ~= "" then
local num = ""
local k = 1
while k <= #params[i] do
keyNum = key..num
citeValues[j][keyNum] = setmetatable({}, {sep={""}, __tostring=toString}) -- "sep" is needed to make this a recognizable array, even though it will not be used
citeValueKeys[j][#citeValueKeys[j] + 1] = keyNum
-- add the external ID's label to the format if we have one
if label ~= "" then
citeValues[j][keyNum][1] = copyValue(params[i][k])
mt2 = getmetatable(citeValues[j][keyNum][1])
mt2.format = mergeArrays({label, " "}, mt2.format or {tostring(citeValues[j][keyNum][1])})
else
citeValues[j][keyNum][1] = params[i][k]
end
k = k + 1
num = k
end
end
else
citeMismatch[j] = true
end
end
end
end
end
-- get title of general template for citing web references
citeWeb = ({split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")})[2] -- split off namespace from front
-- get title of template that expands stated-in references into citations
citeQ = ({split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")})[2] -- split off namespace from front
-- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present
if citeWeb and not citeMismatch['web'] and citeValues['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeValues['web'][i18n['cite']['web'][aliasesP.title]] then
useCite = citeWeb
useValues = citeValues['web']
useValueKeys = citeValueKeys['web']
-- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present
elseif citeQ and not citeMismatch['q'] and citeValues['q'][i18n['cite']['q'][aliasesP.statedIn]] then
-- we need the raw "stated in" Q-identifier for the this template
citeValues['q'][i18n['cite']['q'][aliasesP.statedIn]][1] = self:getReferenceDetail(statement.snaks[aliasesP.statedIn], true)[1] -- raw = true
useCite = citeQ
useValues = citeValues['q']
useValueKeys = citeValueKeys['q']
end
if useCite then
-- make sure that the parameters are added in the exact same order all the time to avoid conflict errors
table.sort(useValueKeys)
-- if this module is being substituted then build a regular template call, otherwise expand the template
if mw.isSubsting() then
mt.format = {"{{", useCite, params={}, req={}}
-- iterate through the sorted keys
for _, key in ipairs(useValueKeys) do
mt2 = getmetatable(useValues[key][1])
mt2.sub = {["|"] = ENC_PIPE}
value[key] = useValues[key]
mt.format[#mt.format + 1] = "|"
mt.format[#mt.format + 1] = key
mt.format[#mt.format + 1] = "="
mt.format[#mt.format + 1] = key
mt.format.params[#mt.format] = true
mt.format.req[key] = true
end
mt.format[#mt.format + 1] = "}}"
else
for _, key in ipairs(useValueKeys) do
value[key] = useValues[key]
end
mt.expand = useCite
end
-- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present
elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then
mt.format = {params={}, req={}}
-- start by adding authors up front
if params[aliasesP.author] then
label = tostring(self.conf:getLabel(aliasesP.author))
if label == "" then
label = aliasesP.author
end
value[label] = params[aliasesP.author]
mt.format[1] = label
mt.format.params[1] = true
mt.format.req[label] = true
mt.format[2] = "; "
end
-- then add "reference URL" and "title", combining them into one link if both are present
if params[aliasesP.referenceURL] then
label = tostring(self.conf:getLabel(aliasesP.referenceURL))
if label == "" then
label = aliasesP.referenceURL
end
value[label] = params[aliasesP.referenceURL]
mt.format[#mt.format + 1] = '['
mt.format[#mt.format + 1] = label
mt.format.params[#mt.format] = true
mt.format.req[label] = true
mt.format[#mt.format + 1] = ' '
if not params[aliasesP.title] then
mt.format[#mt.format + 1] = label
mt.format.params[#mt.format] = true
mt.format.req[label] = true
mt.format[#mt.format + 1] = ']'
else
str = ']'
end
end
if params[aliasesP.title] then
label = tostring(self.conf:getLabel(aliasesP.title))
if label == "" then
label = aliasesP.title
end
value[label] = params[aliasesP.title]
mt.format[#mt.format + 1] = '"'
mt.format[#mt.format + 1] = label
mt.format.params[#mt.format] = true
mt.format.req[label] = true
mt.format[#mt.format + 1] = '"'
mt.format[#mt.format + 1] = str
end
-- then add "stated in"
if params[aliasesP.statedIn] then
label = tostring(self.conf:getLabel(aliasesP.statedIn))
if label == "" then
label = aliasesP.statedIn
end
value[label] = params[aliasesP.statedIn]
mt.format[#mt.format + 1] = "; "
mt.format[#mt.format + 1] = "''"
mt.format[#mt.format + 1] = label
mt.format.params[#mt.format] = true
mt.format.req[label] = true
mt.format[#mt.format + 1] = "''"
end
-- mark previously added parameters so that they won't be added a second time
skipKeys[aliasesP.author] = true
skipKeys[aliasesP.referenceURL] = true
skipKeys[aliasesP.title] = true
skipKeys[aliasesP.statedIn] = true
-- make sure that the parameters are added in the exact same order all the time to avoid conflict errors
table.sort(paramKeys)
-- add the rest of the parameters
for _, key in ipairs(paramKeys) do
if not skipKeys[key] then
label = tostring(self.conf:getLabel(key))
if label ~= "" then
value[label] = params[key]
mt.format[#mt.format + 1] = "; "
mt.format[#mt.format + 1] = label
mt.format[#mt.format + 1] = ": "
mt.format[#mt.format + 1] = label
mt.format.params[#mt.format] = true
mt.format.req[label] = true
end
end
end
mt.format[#mt.format + 1] = "."
end
if not next(params) or not next(value) then
return value -- empty value
end
value[1] = params
mt.hash = statement.hash
if not self.rawValue then
local curTime = ""
-- if this module is being substituted then add a timestamp to the hash to avoid future conflict errors,
-- which could occur when labels on Wikidata have been changed in the meantime while the substitution remains static
if mw.isSubsting() then
curTime = "-" .. self.conf.curTime
end
-- this should become a <ref> tag, so save the reference's hash for later
mt.tag = {"ref", {name = "wikidata-" .. statement.hash .. curTime .. "-v" .. (tonumber(i18n['cite']['version']) + version)}}
end
return value
end
-- gets a detail of one particular type for a reference
function State:getReferenceDetail(snaks, raw, link, anyLang)
local value
local switchLang = anyLang or false
local array = setmetatable({}, {sep={""}, __tostring=toString}) -- "sep" is needed to make this a recognizable array, even though it will not be used
if not snaks then
return array
end
-- if anyLang, first try the local language and otherwise any language
repeat
for _, snak in ipairs(snaks) do
value = self.conf:getValue(snak, raw, link, false, anyLang and not switchLang, false, false, true) -- noSpecial = true
if value[1] then
array[1] = value
return array
end
end
if not anyLang then
break
end
switchLang = not switchLang
until anyLang and switchLang
return array
end
-- gets the details of one particular type for a reference
function State:getReferenceDetails(snaks, raw, link, anyLang, sep)
local value
local array = setmetatable({}, {sep={sep or ""}, __tostring=toString})
if not snaks then
return array
end
for _, snak in ipairs(snaks) do
value = self.conf:getValue(snak, raw, link, false, anyLang, false, false, true) -- noSpecial = true
if value[1] then
array[#array + 1] = value
end
end
return array
end
-- level 1 hook
function State:getAlias(object)
local alias = object.value
local title = nil
if alias 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
return ({buildWikilink(title, alias)})[1]
end
end
return setmetatable({alias}, {__tostring=toString})
end
-- level 1 hook
function State:getBadge(value)
return ({self.conf:getLabel(value, self.rawValue, self.linked, self.shortName)})[1]
end
-- level 1 hook
function State:getSeparator()
return self.conf.movSeparator
end
function State:addToResults(statement)
self.results[#self.results + 1] = self.resultsByStatement[statement][1]
if #self.results == self.maxResults then
return nil
end
return true
end
function State:getAndResetResults()
local results = setmetatable(self.results, {sep=self.separator, datatype=self.resultsDatatype, __tostring=toString})
-- reset results before iterating over next dataset
self.results = {}
self.resultsByStatement = {}
self.resultsDatatype = nil
if self.level == 1 and results[1] and results[#results][parameters.separator] then
results[#results][parameters.separator] = self.conf.puncMark
end
return results
end
-- this function may return nil, in which case the iterate function will break its loop
function State:callHooks(hooks, statement)
local lastResult = nil
local i = 1
-- loop through the hooks in order and stop if one gives a negative result
while hooks[i] do
lastResult = hooks[i](self, statement)
-- check if false or nil
if not lastResult then
return lastResult
end
i = i + 1
end
return lastResult
end
--cycle:
-- iterate(statements, hooks):
-- for statement in statements:
-- valueHook: state.resultsByStatement[statement][param or 1] = func(state, statement, param)
-- func: {if lvl 2 hook}{cycle}
-- {if param}{persistHook: state.resultsByStatement[statement][1][param] = state.resultsByStatement[statement][param]}
-- addToResults(statement): state.results[#state.results + 1] = state.resultsByStatement[statement][1]
-- :rof
-- getAndResetResults: return state.results {finally}{
-- state.results = {}
-- state.resultsByStatement = {}
-- }
--:elcyc
function State:iterate(statements, hooks)
hooks = hooks or self.hooks
for _, statement in ipairs(statements) do
-- call hooks and break if the returned result is nil, which typically happens
-- when addToResults found that we collected the maximum number of results
if (self:callHooks(hooks, statement) == nil) then
break
end
end
end
function State:iterateHooks(claims, hooks)
local i = 1
hooks = hooks or self.hooks
while hooks[i] do
local retry = false
for _, claim in ipairs(claims) do
local result = hooks[i](self, claim)
if not result then
if result == nil then
retry = true
end
break
end
end
if not retry then
i = i + 1
end
end
end
--==-- Public functions --==--
local function claimCommand(args, funcName)
local lastArg, hooks, claims, sortKey, sortKeys
local sortHooks = {}
local value = setmetatable({}, {__tostring=toString})
local cfg = Config:new()
cfg:processCommand(funcName) -- process first command (== function name)
-- 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:processCommandOrFlag(lastArg)
cfg.filterBeforeRank = cfg.filterBeforeRank or not (cfg.periods[1] and cfg.periods[2] and cfg.periods[3])
-- 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])
if cfg.entityID == "" then
return value -- empty; we cannot continue without a valid entity ID
end
if not cfg.propertyID then
cfg.propertyID = nextArg(args)
end
cfg.propertyID = replaceAlias(cfg.propertyID)
if not cfg.propertyID then
return value -- empty; we cannot continue without a property ID
end
cfg.propertyID = cfg.propertyID:upper()
if cfg.statesByParam[parameters.qualifier.."1"] then
-- do further processing if a "qualifier(s)" command was given
if #args - args.pointer + 1 > cfg.qualifiersCount then
-- claim ID or literal value has been given
cfg.propertyValue = nextArg(args)
cfg.filterBeforeRank = true
end
-- for each given qualifier ID, check if it is an alias and add it
for i = 1, cfg.qualifiersCount do
local param
local qualifierID = nextArg(args)
if not qualifierID then
break
end
param = parameters.qualifier..i
qualifierID = replaceAlias(qualifierID):upper()
cfg.qualifierIDs[param] = qualifierID
cfg:addToStatesByID(cfg.statesByParam[param], qualifierID)
end
elseif cfg.statesByParam[parameters.reference] then
-- do further processing if "reference(s)" command was given
cfg.propertyValue = nextArg(args)
cfg.filterBeforeRank = true
end
-- process qualifier matching values, analogous to cfg.propertyValue
for i, v in npairs(args) do
local id = replaceAlias(i):upper()
if isPropertyID(id) then
cfg.qualifierIDsAndValues[id] = v
cfg.filterBeforeRank = true
end
end
-- potential optimization if only 'preferred' ranked claims are desired,
-- or if the 'best' flag was given while no other filter flags were given
if not (cfg.ranks[2] or cfg.ranks[3]) or (cfg.bestRank and not cfg.filterBeforeRank) then
-- returns either only 'preferred' ranked claims or only 'normal' ranked claims
claims = mw.wikibase.getBestStatements(cfg.entityID, cfg.propertyID)
if #claims == 0 then
-- no claims with rank 'preferred' or 'normal' found,
-- property might only contain claims with rank 'deprecated'
if not cfg.ranks[3] then
return value -- empty; we don't want 'deprecated' claims, so we're done
end
claims = nil -- get all statements instead
elseif not cfg.ranks[rankTable[claims[1].rank][1]] then
-- the best ranked claims don't have the desired rank
-- if the best ranked claims have rank 'normal' which isn't desired,
-- then the property might only contain claims with rank 'deprecated'
if claims[1].rank == "normal" and not cfg.ranks[3] then
return value -- empty; we don't want 'deprecated' claims, so we're done
end
claims = nil -- get all statements instead
end
end
if not claims then
claims = mw.wikibase.getAllStatements(cfg.entityID, cfg.propertyID)
end
if #claims == 0 then
return value -- empty; there is no use to continue without any claims
end
-- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration
if not cfg.statesByParam[parameters.property] then
cfg.curState = State:new(cfg, 1, parameters.property, PROP)
-- decrease potential overhead (in case this state will be used for sorting/matching)
cfg.curState.freeNumber = true
-- if the "single" flag has been given then this state should be equivalent to "property" (singular)
if cfg.singleClaim then
cfg.curState.maxResults = 1
end
else
cfg.curState = cfg.statesByParam[parameters.property]
cfg:addToStatesByID(cfg.curState, PROP)
end
-- parse the desired format, or choose an appropriate format
if args["format"] then
hooks = cfg.curState:parseFormat(args["format"])
elseif cfg.statesByParam[parameters.qualifier.."1"] then -- "qualifier(s)" command given
if cfg.statesByParam[parameters.property] then -- "propert(y|ies)" command given
hooks = cfg.curState:parseFormat(formats.propertyWithQualifier)
else
hooks = cfg.curState:parseFormat(formats.qualifier)
end
elseif cfg.statesByParam[parameters.property] then -- "propert(y|ies)" command given
hooks = cfg.curState:parseFormat(formats.property)
else -- "reference(s)" command given
hooks = cfg.curState:parseFormat(formats.reference)
end
hooks[#hooks + 1] = State.addToResults
-- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon
if cfg.statesByParam[parameters.qualifier.."1"] and not cfg.statesByParam[parameters.property] then
cfg.separators["sep%s"][1] = ";"
end
-- if only "reference(s)" has been given, set the default separator to none (except when raw)
if cfg.statesByParam[parameters.reference] and not cfg.statesByParam[parameters.property] and not cfg.statesByParam[parameters.qualifier.."1"]
and not cfg.statesByParam[parameters.reference].rawValue then
cfg.separators["sep"][1] = ""
end
-- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent
if cfg.qualifiersCount == 1 then
cfg.separators["sep%q"] = cfg.separators["sep%q1"]
end
-- process overridden separator values;
-- must come AFTER tweaking the default separators
cfg:processSeparators(args)
-- 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 processing the commands and parsing the format
if cfg.sourcedOnly and not cfg.curState.hooksByParam[parameters.reference] then
if not cfg.statesByParam[parameters.reference] then
local refState = State:new(cfg, 2, parameters.reference)
refState.maxResults = 1 -- decrease overhead
end
cfg.curState:newValueHook(parameters.reference)
end
table.insert(hooks, 1, State.claimMatches)
-- if the best ranked claims are desired, we'll sort by rank first
if cfg.bestRank then
cfg.curState.sortPaths[1] = cfg.curState:prepareSortKey(RANK)
end
if args[p.args.sort] then
sortKeys = args[p.args.sort]
else
sortKeys = RANK -- by default, sort by rank
end
repeat
local sortPath, param, id, newID
sortKey, sortKeys = split(sortKeys, ",")
sortKey = mw.text.trim(sortKey)
-- additional sorting by rank is pointless if only the best rank is desired
if not (cfg.bestRank and sortKey:match('^'..RANK..'[+-]?$')) then
sortPath, param, id, newID = cfg.curState:prepareSortKey(sortKey)
if sortPath then
cfg.curState.sortPaths[#cfg.curState.sortPaths + 1] = sortPath
if param and not cfg.curState.valHooksByIdOrParam[id or param] then
sortHooks[#sortHooks + 1] = newOptionalHook{cfg.curState:newValidationHook(param, id, newID)}
end
end
end
until not sortKeys
cfg.curState:iterate(claims, sortHooks)
table.sort(claims, cfg.curState:newSortFunction())
-- then iterate through the claims to collect values
cfg.curState:iterate(claims, hooks) -- pass property state with level 1 hooks
value = cfg.curState:getAndResetResults()
-- if desired, add a clickable icon that may be used to edit the returned values on Wikidata
if cfg.editable and value[1] then
local mt = getmetatable(value)
mt.trail = cfg:getEditIcon()
end
return value
end
local function generalCommand(args, funcName)
local lastArg
local value = setmetatable({}, {__tostring=toString})
local cfg = Config:new()
-- process command (== function name); if false, then it's not "alias(es)" or "badge(s)"
if not cfg:processCommand(funcName, true) then
cfg.curState = State:new(cfg)
end
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)
if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then
return value -- empty; 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[1] = mw.wikibase.getSitelink(cfg.entityID)
end
if cfg.curState.linked and value[1] then
value = buildWikilink(value[1])
end
elseif funcName == p.generalCommands.description then
value[1] = mw.wikibase.getDescription(cfg.entityID)
else
local values
cfg.entity = mw.wikibase.getEntity(cfg.entityID)
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 value -- empty; there is no use to continue without any aliasses
end
values = cfg.entity.aliases[cfg.langCode]
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 value -- empty; there is no use to continue without any badges
end
cfg.inSitelinks = true
values = cfg.entity.sitelinks[cfg.siteID].badges
end
cfg.separators["sep"][1] = ", "
-- process overridden separator values;
-- must come AFTER tweaking the default separator
cfg:processSeparators(args)
-- iterate to collect values
cfg.curState:iterate(values)
value = cfg.curState:getAndResetResults()
end
-- if desired, add a clickable icon that may be used to edit the returned values on Wikidata
if cfg.editable and value[1] then
local mt = getmetatable(value)
mt.trail = cfg:getEditIcon()
end
return value
end
-- modules that include this module may 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 stringWrapper(frameOrArgs)
local frame, args
-- check if Wikidata is available to prevent errors
if not mw.wikibase then
return ""
end
-- assumption: a frame always has an args table
if frameOrArgs.args then
-- called by wikitext
frame = frameOrArgs
args = copyTable(frame.args)
else
-- called by module
args = frameOrArgs
end
args.pointer = 1
loadI18n(aliasesP, frame)
return tostring(commandFunc(args, commandName))
end
p[commandName] = stringWrapper
local function tableWrapper(args)
-- check if Wikidata is available to prevent errors
if not mw.wikibase then
return nil
end
args = copyTable(args)
args.pointer = 1
loadI18n(aliasesP)
return commandFunc(args, commandName)
end
p["_" .. commandName] = tableWrapper
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)
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