in preprocess/WikiExtractor.py [0:0]
def expandTemplate(self, body):
"""Expands template invocation.
:param body: the parts of a template.
:see http://meta.wikimedia.org/wiki/Help:Expansion for an explanation
of the process.
See in particular: Expansion of names and values
http://meta.wikimedia.org/wiki/Help:Expansion#Expansion_of_names_and_values
For most parser functions all names and values are expanded,
regardless of what is relevant for the result. The branching functions
(#if, #ifeq, #iferror, #ifexist, #ifexpr, #switch) are exceptions.
All names in a template call are expanded, and the titles of the
tplargs in the template body, after which it is determined which
values must be expanded, and for which tplargs in the template body
the first part (default) [sic in the original doc page].
In the case of a tplarg, any parts beyond the first are never
expanded. The possible name and the value of the first part is
expanded if the title does not match a name in the template call.
:see code for braceSubstitution at
https://doc.wikimedia.org/mediawiki-core/master/php/html/Parser_8php_source.html#3397:
"""
# template = "{{" parts "}}"
# Templates and tplargs are decomposed in the same way, with pipes as
# separator, even though eventually any parts in a tplarg after the first
# (the parameter default) are ignored, and an equals sign in the first
# part is treated as plain text.
# Pipes inside inner templates and tplargs, or inside double rectangular
# brackets within the template or tplargs are not taken into account in
# this decomposition.
# The first part is called title, the other parts are simply called parts.
# If a part has one or more equals signs in it, the first equals sign
# determines the division into name = value. Equals signs inside inner
# templates and tplargs, or inside double rectangular brackets within the
# part are not taken into account in this decomposition. Parts without
# equals sign are indexed 1, 2, .., given as attribute in the <name> tag.
if self.frame.depth >= self.maxTemplateRecursionLevels:
self.recursion_exceeded_2_errs += 1
# logging.debug('%*sEXPAND> %s', self.frame.depth, '', body)
return ''
logging.debug('%*sEXPAND %s', self.frame.depth, '', body)
parts = splitParts(body)
# title is the portion before the first |
title = parts[0].strip()
title = self.expand(title)
# SUBST
# Apply the template tag to parameters without
# substituting into them, e.g.
# {{subst:t|a{{{p|q}}}b}} gives the wikitext start-a{{{p|q}}}b-end
# @see https://www.mediawiki.org/wiki/Manual:Substitution#Partial_substitution
subst = False
if re.match(substWords, title, re.IGNORECASE):
title = re.sub(substWords, '', title, 1, re.IGNORECASE)
subst = True
if title in self.magicWords.values:
ret = self.magicWords[title]
logging.debug('%*s<EXPAND %s %s', self.frame.depth, '', title, ret)
return ret
# Parser functions.
# For most parser functions all names and values are expanded,
# regardless of what is relevant for the result. The branching
# functions (#if, #ifeq, #iferror, #ifexist, #ifexpr, #switch) are
# exceptions: for #if, #iferror, #ifexist, #ifexp, only the part that
# is applicable is expanded; for #ifeq the first and the applicable
# part are expanded; for #switch, expanded are the names up to and
# including the match (or all if there is no match), and the value in
# the case of a match or if there is no match, the default, if any.
# The first argument is everything after the first colon.
# It has been evaluated above.
colon = title.find(':')
if colon > 1:
funct = title[:colon]
parts[0] = title[colon + 1:].strip() # side-effect (parts[0] not used later)
# arguments after first are not evaluated
ret = callParserFunction(funct, parts, self)
logging.debug('%*s<EXPAND %s %s', self.frame.depth, '', funct, ret)
return ret
title = fullyQualifiedTemplateTitle(title)
if not title:
self.template_title_errs += 1
return ''
redirected = options.redirects.get(title)
if redirected:
title = redirected
# get the template
if title in options.templateCache:
template = options.templateCache[title]
elif title in options.templates:
template = Template.parse(options.templates[title])
# add it to cache
options.templateCache[title] = template
del options.templates[title]
else:
# The page being included could not be identified
logging.debug('%*s<EXPAND %s %s', self.frame.depth, '', title, '')
return ''
logging.debug('%*sTEMPLATE %s: %s', self.frame.depth, '', title, template)
# tplarg = "{{{" parts "}}}"
# parts = [ title *( "|" part ) ]
# part = ( part-name "=" part-value ) / ( part-value )
# part-name = wikitext-L3
# part-value = wikitext-L3
# wikitext-L3 = literal / template / tplarg / link / comment /
# line-eating-comment / unclosed-comment /
# xmlish-element / *wikitext-L3
# A tplarg may contain other parameters as well as templates, e.g.:
# {{{text|{{{quote|{{{1|{{error|Error: No text given}}}}}}}}}}}
# hence no simple RE like this would work:
# '{{{((?:(?!{{{).)*?)}}}'
# We must use full CF parsing.
# the parameter name itself might be computed, e.g.:
# {{{appointe{{#if:{{{appointer14|}}}|r|d}}14|}}}
# Because of the multiple uses of double-brace and triple-brace
# syntax, expressions can sometimes be ambiguous.
# Precedence rules specifed here:
# http://www.mediawiki.org/wiki/Preprocessor_ABNF#Ideal_precedence
# resolve ambiguities like this:
# {{{{ }}}} -> { {{{ }}} }
# {{{{{ }}}}} -> {{ {{{ }}} }}
#
# :see: https://en.wikipedia.org/wiki/Help:Template#Handling_parameters
params = parts[1:]
# Order of evaluation.
# Template parameters are fully evaluated before they are passed to the template.
# :see: https://www.mediawiki.org/wiki/Help:Templates#Order_of_evaluation
if not subst:
# Evaluate parameters, since they may contain templates, including
# the symbol "=".
# {{#ifexpr: {{{1}}} = 1 }}
params = [self.transform(p) for p in params]
# build a dict of name-values for the parameter values
params = self.templateParams(params)
# Perform parameter substitution.
# Extend frame before subst, since there may be recursion in default
# parameter value, e.g. {{OTRS|celebrative|date=April 2015}} in article
# 21637542 in enwiki.
self.frame = self.frame.push(title, params)
instantiated = template.subst(params, self)
value = self.transform(instantiated)
self.frame = self.frame.pop()
logging.debug('%*s<EXPAND %s %s', self.frame.depth, '', title, value)
return value