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.

Usage edit source

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 cases edit source

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 raw flag is important to include. Without this flag, an existing item's regular label would be returned which could be empty, giving an undesired result. If the flag is given on the other hand, then a non-empty Q-identifier is always returned if an item exists and an empty value if an item does not exist.

Commands edit source

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 class edit source

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}}
  1. 1.0 1.1 1.2 Returns only a single value instead of multiple (if multiple claims or statements match). The returned value is the first match found from the best-ranked claims.
  2. Only references that are valid according to the Wikidata guideline on sources are returned (i.e. those that have at least a property Template:Wpl or Template:Wpl).

General class edit source

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}}
  1. 1.0 1.1 Returns only a single value instead of multiple (if multiple values are present).

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. {{wikidata|property|P1549}}{{#invoke:wd|main}}{{#invoke:wd|main|property|P1549}}.

Therefore, any parameters set by the wrapper template itself will be discarded, e.g. {{wikidata|property|P1549}}{{#invoke:wd|main|qualifier|P1082|P585}}{{#invoke:wd|main|property|P1549}}.

Flags edit source

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 flags edit source

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 unit flag is also given in which case the raw unit of measurement (its Q-identifier) will be returned.

If this flag is used with time datatypes, then the returned date will be in the format of yyyy-mm-dd (e.g. 1731-02-11), or yyyy-mm, or yyyy depending on the date's precision. Dates in the Julian calendar stored with a precision of days through millenniums will have /Julian attached to the output (e.g. 1731-02-11/Julian, which may be split off using the {{#titleparts}} template function).

If it is used with globe coordinate datatypes, then it replaces the various symbols with forward slashes in the returned value (e.g. 52/5/3/N/4/19/3/E, which may be split into parts using the {{#titleparts}} template function).

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 flags edit source

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 + or a -, e.g. normal+ or preferred-, where the first selects claims with a 'normal' rank or higher and the second selects claims with a 'preferred' rank or lower. To get claims of all ranks, use preferred- or deprecated+.

If the best flag is given additionally, then only the claims that have the highest rank amongst the selected claims will be returned.

The default is normal+|best (so by default claims with a 'deprecated' rank are never returned).

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 future|current|former (so by default claims that are valid for any time period are returned), except when date= is given (see below) in which case the default is current.

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 edit@end is used, then the icon will be placed at the end of the line for neat alignment in infoboxes.

claim, general
edit@end

Arguments edit source

The arguments determine the sources from which all the returned values are fetched.

Positional arguments edit source

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 Property: prefix may be omitted for P-identifiers (e.g. P38).

If this parameter is omitted, then the item-entity connected to the current page will be used (except when eid= or page= is given, see below). If this parameter is given, but empty (i.e. ||), then due to its position it will be interpreted as an empty flag and thus be ignored, giving the same result as if it were omitted. See also the named arguments eid= and page= below that can be used to give an entity-ID or page title too, but without the item-entity connected to the current page being used as a default.

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 yyyy-mm-dd (e.g. 1731-02-11) for dates with a precision of days, yyyy-mm (e.g. 1731-02) for dates with a precision of months, and yyyy (e.g. 1731) for dates of lesser precision. Dates BCE require a minus sign in front of the year (e.g. -2950-01-31). Dates stored in the Julian calendar must have /Julian attached to the end (e.g. 1731-02-11/Julian). Decades like the 2010s must be given as 2010 (but the 2010s BCE as -2019), centuries like the 20th century as 1901 (but the 20th century BCE as -2000), and millenniums like the 3rd millennium as 2001 (but the 3rd millennium BCE as -3000).

Globe coordinates as literal values must be formatted with forward slashes (i.e. /) between the parts and no symbols (e.g. 52/5/3/N/4/19/3/E) without any spaces or leading zeros.

The special type 'no value' can be given by entering the empty string (i.e. ||) and the special type 'unknown value' can be given by entering a single underscore (i.e. |_|). 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 |.

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 arguments edit source

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 Property: prefix may be omitted for P-identifiers (e.g. |eid=P38) for all commands.

This argument only has effect if the positional argument entity_id is omitted.

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 entity_id and the named argument eid= are omitted.

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 yyyy-mm-dd (e.g. 1731-02-11), yyyy-mm (e.g. 1731-02) or yyyy (e.g. 1731).

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, <qualifier> is any qualifier's P-identifier (or an available alias) without the Property: prefix (e.g. P518). Its value is either the Q-identifier equal to one of the qualifier values (e.g. Q27561) or a literal value (i.e. string or quantity etc., no entity label) equal to one of the raw qualifier values of the particular claim to be accessed. The value format is the same as for the positional argument raw_value. The special type 'no value' given by the empty string also matches the total absence within the claim of the particular qualifier.

Example: |P518=Q27561

Multiple arguments of this type can be given to match multiple qualifier values simultaneously for each claim.

claim

Property aliases edit source

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 usage edit source

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 &#124;.

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.
Parameter Description
%p The claim's property value applied by the property/properties command.
%q1, %q2, %q3, ... The claim's qualifier value or list of qualifier values applied by the corresponding qualifier/qualifiers command.
%q The collection of the qualifier values applied by each qualifier/qualifiers command (i.e. %q1 + %q2 + %q3 + ...). If only one qualifier/qualifiers command was given, then this parameter is equivalent to %q1.
%r The claim's reference value or list of reference values applied by the reference/references command.
%a The entity's alias applied by the alias/aliases command.
%b The entity's page badge applied by the badge/badges command.
%s The movable separator placeholder. This is a special parameter that is not applied by a command, but instead is filled automatically between each pair of claims, aliases or badges (if a list of claims, aliases or badges is returned). This is particularly handy in case a claim's reference is returned as well, since it allows the reference to be placed after the punctuation mark as prescribed by Wikipedia's manual of style. The default value is a comma (,) and can be overridden with the sep%s flag (see below).

Optional parameters can be given by encapsulating them between square brackets: [...]. All content between the square brackets is only displayed if a value for each optional parameter that has been defined between the same brackets has been found. Optional content can also be nested.

To use two opening square brackets that directly follow each other (i.e. Template:!((), use {{!((}}.

At least one parameter must be given that is not optional, while the %s parameter must always be defined as optional.

To get a literal [, ], % or \, escape the character by placing a backslash \ directly in front of it (e.g. \%). See also the description directly above this table for more.

%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 qualifier/qualifiers command was given, then the sep%q1 flag is equivalent to sep%q.

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 qualifier/qualifiers command was given, then this flag is equivalent to sep%q1.

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.

Examples edit source

Parameters and output types Example Description
Q55 = "", P395 = ""

[[[:Template:Smallcaps]]]

{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|Q55|P395}}

Gets a literal string value.
P395 = ""

[[[: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 = "", P395 = ""

[[[:Template:Smallcaps]]]

{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|eid=Q55|P395}}

An entity-ID can also be given using the eid= argument.
P395 = ""

[[[: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 = "", P395 = ""

[[[: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 = "", P395 = ""

[[[:Template:Smallcaps]]]

{{Template:Hashinvoke:Sandbox/Thayts/Wd|property|edit@end|Q55|P395}}

Places the edit icon at the end of the line.
Q55 = "", P1082 = ""

[[[: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 = "", P1082 = ""

[[[:Template:Smallcaps]]]

{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|normal+|Q55|P1082}}

Gets multiple property values from claims with a 'normal' rank or higher.
Q55 = "", P1082 = "", P585 = ""

[[[: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 = "", P1082 = "", P585 = ""

[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]]

{{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifier|references|normal+|Q55|P1082|P585}}

Gets references for each claim.
Q55 = "", P1082 = ""

[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]]

A total of {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|references|Q55|P1082}} people live in the Netherlands.

A total of people live in the Netherlands.
Gets a property with its references.
Q55 = "", P1082 = ""

[[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]]

The Netherlands has a population of {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|references|Q55|P1082|punc=.}}

The Netherlands has a population of
Adds a punctuation mark at the end of the output, in front of the references.
Q55 = "", P1082 = "", P585 = ""

[[[: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 = "", P1082 = "", P585 = ""

    [[[: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 = "", P1082 = "", P585 = ""

    [[[: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 = "", P1082 = "", P585 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|qualifier|Q55|P1082|'|P585}}

    Gets a qualifier from claims for which the (raw) property value matches a given literal value.
    Q55 = "", P1082 = "", P585 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|qualifier|mdy|Q55|P1082||P585}}

    Gets dates in month-day-year order.
    Q55 = "", P1082 = "", P585 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|qualifier|raw|Q55|P1082||P585}}

    Gets a raw date value.
    Q55 = "", P1082 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|'references|Q55|P1082|'}}

    Gets the references from a particular claim.
    Q55 = "", P1082 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|'references|raw|Q55|P1082|'}}

    Gets references from a particular claim in their raw form.
    Q55 = "", P1081 = ""

    [[[: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 = "", P1081 = ""

    [[[: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 = "", P2855 = "", P518 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|qualifier|Q55|P2855|P518}}

    Gets a single qualifier value (for each matching claim).
    Q55 = "", P2855 = "", P518 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|qualifiers|Q55|P2855|P518}}

    Gets multiple qualifier values (for each matching claim).
    Q55 = "", P2855 = "", P518 = ""

    [[[:Template:Smallcaps]]], [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifiers|Q55|P2855|P518}}

    Gets multiple property values along with multiple qualifier values.
    Q55 = "", P2855 = "", P518 = ""

    [[[: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 = "", P35 = "", P580 = "", P582 = ""

    [[[: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 = "", P35 = "", P580 = "", P582 = ""

    [[[: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 = "", P35 = "", P580 = "", P582 = ""

    [[[: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 = "", P35 = "", P580 = "", P582 = ""

    [[[: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 = "", P35 = "", Q29574 = "", P580 = "", P582 = ""

    [[[: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 = "", P38 = "", P518 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifiers|normal+|current|Q55|P38|P518}}

    Gets claims that are currently valid.
    Q55 = "", P38 = "", P518 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|linked|qualifiers|normal+|current|Q55|P38|P518}}

    Gets claims with linked property values.
    Q55 = "", P38 = "", P518 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|qualifiers|linked|normal+|current|Q55|P38|P518}}

    Gets claims with linked qualifier values.
    Q55 = "", P38 = "", P518 = ""

    [[[: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 = "", P38 = "", Q4917 = "", P518 = ""

    [[[: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 = "", P38 = "", P518 = "", 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 = "", P38 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|normal+|former|Q55|P38}}

    Gets claims that were valid in the past.
    Q55 = "", P38 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|raw|normal+|former|Q55|P38}}

    Gets raw property values.
    Q55 = "", P38 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|properties|raw|linked|normal+|former|Q55|P38}}

    Gets raw property values that are linked to Wikidata.
    Q55 = "", P1549 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|Q55|P1549}}

    Gets a monolingual text value in the current wiki's language.
    Q55 = "", P1549 = "", P407 = "", Q36846 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|multilanguage|Q55|P1549|P407=Q36846}}

    Gets a monolingual text value in any available language.
    Q55 = "", P2884 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|Q55|P2884}}

    Gets a quantity value with its associated unit of measurement.
    Q55 = "", P2884 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|linked|Q55|P2884}}

    Gets a quantity value with a linked unit of measurement.
    Q55 = "", P2884 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|raw|Q55|P2884}}

    Gets a raw quantity value.
    Q55 = "", P2884 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|unit|Q55|P2884}}

    Gets only the unit of measurement.
    Q55 = "", P2884 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|unit|raw|Q55|P2884}}

    Gets the raw unit of measurement.
    Q55 = "", P625 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|Q55|P625}}

    Gets a globe coordinate value.
    Q55 = "", P625 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|linked|Q55|P625}}

    Gets a linked globe coordinate value.
    Q55 = "", P625 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|raw|Q55|P625}}

    Gets a raw globe coordinate value.
    Q55 = "", P625 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|Q55|coord}}

    A property alias can be used instead of the P-identifier.
    Q55 = "", P41 = ""

    [[[:Template:Smallcaps]]]

    {{Template:Hashinvoke:Sandbox/Thayts/Wd|property|linked|Q55|P41}}

    Gets a media file name and links to it on Commons.
    Q55 = "", P41 = ""

    [[[: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 = "", P41 = ""

    [[[: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 = "", P41 = ""

    [[[: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 = ""

    [[[: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 = "", P3896 = ""

      [[[: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 = ""

      [[[:Template:Smallcaps]]]

      {{Template:Hashinvoke:Sandbox/Thayts/Wd|label|Q4917}}

      Gets an item's label.
      Q4917 = ""

      [[[:Template:Smallcaps]]]

      {{Template:Hashinvoke:Sandbox/Thayts/Wd|label|short|linked|Q4917}}

      Gets an item's short and linked label.
      P38 = ""

      [[[:Template:Smallcaps]]]

      {{Template:Hashinvoke:Sandbox/Thayts/Wd|label|P38}}

      Gets a property's label.
      P38 = ""

      [[[:Template:Smallcaps]]]

      {{Template:Hashinvoke:Sandbox/Thayts/Wd|label|linked|P38}}

      Gets a property's label that is linked to Wikidata.
      Q776 = ""

      [[[:Template:Smallcaps]]]

      {{Template:Hashinvoke:Sandbox/Thayts/Wd|label|Q776}}

      Gets an item's label.
      Q776 = ""

      [[[: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 = ""

      [[[: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 = ""

      [[[: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 = ""

      [[[: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 = ""

      [[[:Template:Smallcaps]]]

      {{Template:Hashinvoke:Sandbox/Thayts/Wd|alias|Q55}}

      Gets one of an item's aliases.
      Q55 = ""

      [[[:Template:Smallcaps]]]

      {{Template:Hashinvoke:Sandbox/Thayts/Wd|aliases|Q55}}

      Gets all of an item's aliases.
      Q55 = ""

      [[[: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 = ""

      [[[: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 = ""

      [[[: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 references edit source



      -- 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     = "&#160;"
      local ENC_PIPE     = "&#124;"
      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.."&params="..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