[go: up one dir, main page]

Zum Inhalt springen

Modul:Marker utilities

Aus Wikivoyage
Dokumentation für das Modul Marker utilities[Ansicht] [Bearbeiten] [Versionsgeschichte] [Aktualisieren]

Anwendung

Das Modul stellt gemeinsame Funktionen für das Modul:Marker und das Modul:vCard zur Verfügung. Im Projektnamensraum befindet sich die technische Dokumentation.

Versionsbezeichnung auf Wikidata: 2024-11-11 Ok!

Benötigte weitere Module

Dieses Modul benötigt folgende weitere Module: Coordinates • Marker utilities/Groups • Marker utilities/i18n • Marker utilities/Maki icons • Marker utilities/Types • UrlCheck • Wikidata utilities

Verwendung in anderen Modulen

Dieses Modul ist notwendig für die Ausführung folgender Module. Bei Anpassungen sollte die Funktionstüchtigkeit der folgenden Module geprüft werden. Benutze dazu auch diese Tracking-Kategorie um Fehler zu finden, die sich dann auf Artikel auswirken:

Wartungsfunktionen

function mu.initMaintenance( name )

Die Funktion initialisiert die Zeichenkettenverwaltung für die Ausgabe von Fehlermeldungen. name ist der zugehörige Modulname ohne die Namensraumbezeichnung. Diese Funktion setzt auch die Tabellen fehlerhafter Paramter und der Fehlermeldungen/Hinweise zurück.

function mu.addMaintenance( key, value )

Diese Funktion fügt die Fehlermeldung mit dem Schlüssel key in die Tabelle der Fehlermeldungen und Hinweise ein. value dient als Ersatz für einen Platzhalter %s. Ein Teil der oben genannten Funktionen befüllt ebenfalls diese Tabelle.

function mu.makeMaintenance( page, modules )

Diese Funktion liefert eine Zeichenkette mit allen Fehlermeldungen und Hinweisen zurück. modules ist eine Tabelle mit den Modulvariablen, aus denen weitere Wartungskategorien erhalten werden sollen.

function mu.getCategories( formatStr )
  • Liefert eine Zeichenkette mit den Kategorie-Links aller verwendeten Wikidata-Eigenschaften zurück.

Funktionen für allgemeine Nutzung

function mu.isSet( arg )

liefert true oder false, je nachdem, ob das Argument arg gesetzt ist oder nicht. arg muss existieren und einen Wert ungleich '' enthalten.

function mu.convertForSort( s )

Wandelt die Zeichenkette s so um, dass eine korrekte Sortierung ermöglicht wird. Die auszutauschenden Buchstaben sind in der sprachabhängigen Tabelle substitutes im Modul Marker utilities/i18n enthalten.

function mu.formatNumber( num )

Die Funktion ersetzt in der als Zeichenkette vorliegenden Zahl das Dezimalzeichen und fügt Tausendertrennzeichen ein.

function mu.tableInsert( tab, value )

fügt den Wert value zur Tabelle tab hinzu, wenn er existiert und nicht leer ist.

function mu.textSplit( s, sep )

trennt die Zeichenkette s an Trennzeichen sep auf und legt sie in einer Tabelle ab, die aber keine leeren Zeichenketten enthält. sep muss genau die Länge von einem Zeichen (ein Byte) haben und darf kein magisches Pattern-Zeichen sein. Die Funktion ist deutlich schneller als mw.text.split().

function mu.split( s )

liefert eine assoziative Tabelle kommaseparierter Werte der Zeichenkette s. Die Teilzeichenketten werden in Kleinbuchstaben umgewandelt, und Leerräume werden durch den Unterstrich ersetzt.

function mu.makeSpan( s, class, isBdi, attr, css )

liefert eine Zeichenkette, die von einem <span>-Tag oder <bdi>-Tag umschlossen ist. class ist das Klassenattribut des Tags, während die Tabellen attr und css weitere Tap-parameter liefern.

function mu.languageSpan( s, titleHint, args, country, addClass )

fügt den Text in ein span-Tag, in dem die Sprache und Textrichtung des des Texts s angegeben ist. titleHint ist das title-Attribut des span-Tags, args die Vorlagenparamtetertabelle und country eine Tabelle mit landesspezifischen Angaben. addClass stellt einen zusätzlichen Klassenbezeichner dar.

function mu.addWdClass( isFromWikidata )

liefert den Klassenbezeichner wikidata-content, wenn isFromWikidata auf true gesetzt ist, ansonsten eine leere Zeichenkette.

function mu.getAliases( tab, key )

erstellt aus der Tabelle tab eine Alias-Tabelle, die in tab unter dem Schlüssel key notiert sind.

function mu.yesno( val )

gibt y oder n zurück, wenn val einen entsprechenden Wert besitzt, anderenfalls nil.

Prüffunktionen

function mu.checkArguments( templateArgs, validKeys )

prüft, ob im Vorlagenaufruf unbekannte oder durch Aliase entstandene doppelte Parameternamen verwendet werden. Diese unbekannten bzw. doppelten Parameter werden in die Tabelle fehlerhafter bzw. doppelter Parameter eingefügt. templateArgs ist die Tabelle der Vorlagenparamter, validKeys die Tabelle der erlaubten Parameter (siehe z. B. Modul:VCard/i18n). Die Funktion liefert eine Tabelle der gültigen Argumente zurück.

function mu.checkCommonsCategory( args )

Die Funktion löscht eine evtl. vorhandene Namensraumangabe in der Commons-Kategorie args.commonscat und fügt eine Wartungskategorie hinzu, wenn die Commons-Kategorie gesetzt wurde.

function mu.checkCoordinates( lat, long )

liefert die überprüften Koordinaten lat, long. Im Fehlerfall sind lat und long leere Zeichenketten. Die Fehlermeldungstabelle enthält zusätzlich einen entsprechenden Eintrag (siehe unten).

function mu.checkZoom( args )

ersetzt args.zoom mit einem Standardwert, falls args.zoom nicht im Intervall von [0, 19] liegt.

function mu.checkImage( image, entity )

liefert den überprüften Wert für das image oder eine leere Zeichenkette zurück. Im Fehlerfall enthält die Fehlermeldungstabelle zusätzlich einen entsprechenden Eintrag (siehe unten). Die Variable mi.options.imageCheck legt fest, ob überhaupt einen rechenzeitintensive Prüfung vorgenommen wird.

function mu.checkStatus( args )

prüft die übergebenen Werte im Parameter status und legt die gültigen Werte in args.statusTable ab. Im Fehlerfall enthält die Fehlermeldungstabelle zusätzlich einen entsprechenden Eintrag (siehe unten).

function mu.checkStyles( args )

ersetzt Stil-Aliase durch CSS-Stile.

function mu.checkTypeAndGroup( args )

liefert die überprüften Werte für den Typ und die Gruppen in args zurück. Im Fehlerfall enthält die Fehlermeldungstabelle zusätzlich einen entsprechenden Eintrag (siehe unten).

function mu.checkUrl( args )

liefert die überprüfte Internetaddresse url in der Argumenttabelle args zurück. Im Fehlerfall enthält die Fehlermeldungstabelle zusätzlich einen entsprechenden Eintrag (siehe unten).

Typ- und Gruppenfunktionen

function mu.getTypeParams( aType )

liefert den Parametersatz aus Module:Marker utilities/Types für den Typ aType oder nil zurück.

function mu.getTypeLabel( id )

liefert das erste Label aus der Typenliste zum id. id kann ein Marker- oder vCard-Typ bzw. eine Wikidata-Id sein. Im Fehlerfall wird eine leere Zeichenkette oder der id selbst zurückgegeben.

function mu.typeExists( aType )

liefert den Typ aType oder nil zurück, je nachdem, ob der Typ in der Typentabelle enthalten ist. Aliase werden in den zugehörigen Typ umgewandelt.

function mu.groupWithEvents( group )

prüft, ob in der angegebenen Gruppe group Ereignisse als Typen vorgesehen sind.

function mu.getColor( args )

fügt color und inverse zu den Argumenten args aus der Gruppe group hinzu. color ist die zur Gruppe gehörende Farbe und wird aus der Tabelle Modul:Marker utilities/Groups bezogen.

function mu.idToType( id )

liefert zur Wikidata-Id den zugehörigen Typ oder nil zurück.

Funktionen zur Parameteraufbereitung

function mu.getShow( default, args, validValues )

liefert eine assoziierte Tabelle mit den übergebenen kommaseparierten show-Attributen, wobei der überschreibbare Standardwert default berücksichtigt wird. args ist die Parametertabelle. Die args.show-Attribute werden auf Gültigkeit hin überprüft.

function mu.removeCtrls( s, onlyInline )

Die Funktion entfernt Steuerzeichen und die HTML-Tags für den Zeilenumbruch und Zeilenwechsel aus der Zeichenkette s. Wenn onlyInline = false, dann bleiben Zeilenumbruch und Zeilenwechsel erhalten. Deren Vorkommen wird in der Variablen descrDiv mitgeteilt. Die Funktion liefert zwei Werte zurück:

  • s bereinigte Zeichenkette.
  • descrDiv Der Container für die Beschreibung muss ein <div>-Tag sein.

Wikidata-Abfragefunktionen

function mu.getCoordinatesFromWikidata( entity )

liefert die im Wikidata-Datensatz mit der Einität entity enthaltene Koordinate zurück. Zuerst wird versucht, die Zentrumskoordinate aus der Eigenschaft P5140 zu erhalten, danach die Koordinate aus der Eigenschaft P625.

function mu.typeSearch( p31 )

Sucht in mehreren P31-P279-Ketten nach Q-ids, deren Werte in der Tabelle Module:Marker utilities/Types enthalten sein könnten. Die Tabelle p31 enthält die vorgefunden P31-Werte. Der erste Treffer wird als Zeichenkette zurückgegeben, im Fehlerfall die Zeichenkette error. Die Maximalanzahl höherer Ebenen für die Suche ist mit mi.searchLimit vorgegeben, dies sind üblicherweise 4. Es wird nur jeweils die erste P279-Id ausgewertet, also nicht die gesamte Baumstruktur.

function mu.getCommonsCategory( args, entity )

Die Funktion versucht, eine Commons-Kategorie aus Wikidata über die Wikidata-Entität entity zu beziehen. Zuerst wird der Commons-Site-Link analysiert, dann die Eigenschaft P373 und zuletzt die Eigenschaft P910.

function mu.getLangTable( wikiLang, localLang )

erstellt eine Tabelle mit Sprachbezeichnern unter Nutzung von wikiLang, localLang (country.lang) und den Sprachen langs aus dem Modul Marker utilities/i18n.

function mu.getNamesFromWikidata( args, fromWikidata, country, entity )

Die Funktion befüllt die Tabelle args mit dem Namen der Einrichtung in der Wiki-Sprache und in der Landessprache mit den Angaben aus Wikidata. Die Tabelle fromWikidata enthält die Information (fromWikidata.name, fromWikidata.nameLocal), ob der Name aus Wikidata stammt.

function mu.getArticleLink( args, entity )

Die Funktion übergibt den Sitelink zum zugehörigen Arikel an args.thisWiki, außer der Vorlagenaufruf wurde in diesem Arikel vorgenommen.

Marker-Funktionen

function mu.makeMarkerSymbol( args, title, frame, show )

liefert r: HTML-Quellcode des Marker-Symbols.

Symbolfunktionen

function mu.makeStatusIcons( args )

liefert eine Zeichenkette mit der Bildersyntax gemäß der Tabelle args.statusTable zurück.

function mu.addLinkIcon( classes, link, title, text )

erstellt den HTML-Code für die Anzeige eines verlinkten Symbolbildes. Die Darstellung erfolgt im Zusammenspiel mit Stilvorlagen. Gefordert werden zu listing-icon hinzuzufügende CSS-Klassen classes, ein Link link (entweder Internet- oder Artikellink), ein Tooltip-Text title und der meist nicht sichtbare Linktext text.

function mu.makeIcons( args, page, country, entity, show, fromWikidata )

liefert die verlinkten Schwesterprojekt-Symbole und die verlinkten Symbole von Social-Media-Diensten zurück. Die Angaben stammen meist aus den Sitelinks der Wikidata-Entity entity. args ist die Tabelle der übergebenen Vorlagenparameter. Die Tabelle country enthält länderspezifische Daten wie die Sprachangabe. fromWikidata die Tabelle der Parameter, die aus Wikidata bezogen wurden.

Ausgabefunktionen

function mu.prepareNames( args )

fügt der Argumente-Tabelle den Tabellen displayName und givenName hinzu, die aus den Parametern name und nameMap gebildet werden. Hauptaufgabe ist es, den eigentlichen Namen aus einem möglichen Link in Wikiyntax herauszulösen. Beide Tabellen bestehen aus folgenden drei Elementen:

  • name: string, nur der Name.
  • all: string, verlinkter Name oder nur der Name, falls kein Link vorliegt.
  • pageTitle: string, Artikel, auf den verlinkt wird. Der Titel kann von name verschieden sein.
function mu.makeName( result, args, show, page, country, nameClass, localClass )

fügt den Namen und Namensergänzungen zur Tabelle result hinzu. Zu den Namensergänzungen gehören der alternative Name, der lokale Name, Kommentar und Flughafencodes, die in Klammern geschrieben und mit einem span-Tag umschlossen werden. nameClass und localClass sind zusätzliche Klassenattribute für den Namen und den lokalen Namen.

function mu.parentheses( s, trim )

fügt die Zeichenkette s in Klammern ein. Wenn trim = true wird die Formatierungszeichenkette (üblicherweise ' (%s)') getrimmt.

function mu.dmsCoordinates( lat, long, name, fromWD, extraParams, noBrackets )

liefert die Zeichenkette r, die eine zu den Kartenwerkzeugen verlinkte Dezimalkoordinate enthält, die wahlweise in Klammern gesetzt werden kann. name ist der Name der Einrichtung, fromWD = true besagt, dass die Koordinate aus Wikidata stammt und extraParams enthält zusätzliche Parameter wird Maßstab und Region, die in den Kartenwerkzeugen ausgewertet werden.

function mu.makeWrapper( result, args, country, show, list, aClass, frame )

umgibt zum Inhalt eines Markers oder einer vCard mit einem umschließenden Tag (span, div).

Sonstige Funktionen

function mu.getPageData()
stellt seitenbezogene Daten zur Verfügung.
Hinweise
--[[
	Functions library for Marker and vCard modules
	In non-Wikivoyage projects, sister-project links functions have to be adapted.
]]--

-- require( 'strict' )
local cd = require( 'Module:Coordinates' )
local mg = require( 'Module:Marker utilities/Groups' )
local mi = require( 'Module:Marker utilities/i18n' )
local mm -- MAKI icons
local mt = require( 'Module:Marker utilities/Types' )   -- types to groups like drink, eat, go, see, sleep, ...
local uc -- URL check
local wu = require( 'Module:Wikidata utilities' )

-- module variable and administration
local mu = {
	moduleInterface = {
		serial = '2024-11-11',
		item   = 58187612
	},
	comma = mi.texts.comma,
	commaSeparator = mi.texts.commaSeparator
}

local colorAdjust = { ['-webkit-print-color-adjust'] = 'exact', ['color-adjust'] = 'exact',
	['print-color-adjust'] = 'exact' }

-- maintenance tools
function mu.initMaintenance( name )
	mu.invalidParams    = {}   -- table of unknown parameters
	mu.duplicateAliases = {}   -- table of duplicate parameter aliases
	mu.maintenance      = {}   -- table of error strings

	mu.types            = mt.types
	mu.groups           = mg.groups
	mu.typeAliases      = nil  -- table for type aliases. Create on demand
	mu.groupAliases     = nil  -- table for group aliases
end

local function contains( new )
	for i = 1, #mu.maintenance do
		if mu.maintenance[ i ] == new then
			return true
		end
	end
	return false
end

function mu.addMaintenance( key, value )
	local s = key -- fallback
	local tab = mi.maintenance[ key ]
	if tab then
		s = mi.formats.category:format( tab.category ) ..
			( tab.err and mi.formats.error:format( tab.err ) or '' ) ..
			( tab.hint and mi.formats.hint:format( tab.hint ) or '' )
	end
	s = value and mw.ustring.format( s, value ) or s

	if not contains( s ) then
		table.insert( mu.maintenance, s )
	end
end

function mu.getCategories( formatStr )
	return wu.getCategories( formatStr )
end

local function getMaintenance()
	if #mu.invalidParams == 1 then
		mu.addMaintenance( 'unknownParam', mu.invalidParams[ 1 ] )
	elseif #mu.invalidParams > 1 then
		mu.addMaintenance( 'unknownParams',
			table.concat( mu.invalidParams, mu.commaSeparator ) )
	end
	if #mu.duplicateAliases > 0 then
		mu.addMaintenance( 'duplicateAliases',
			table.concat( mu.duplicateAliases, mu.commaSeparator ) )
	end
	return table.concat( mu.maintenance, '' )
end

function mu.makeMaintenance( page, modules )
	if mi.nsNoMaintenance[ page.namespace ] then
		return ''
	end

	local r = getMaintenance()
	if mi.options.usePropertyCateg then
		local m = mi.maintenance.properties -- format string
		for i, aModule in ipairs( modules ) do
			if aModule then
				r = r .. aModule.getCategories( m )
			end
		end
	end
	return r
end

-- general-use functions
function mu.isSet( arg )
	return arg and arg ~= ''
end

function mu.convertForSort( s )
	s = s:ulower()
	for i, obj in ipairs( mi.substitutes ) do
		s = mw.ustring.gsub( s, obj.l, obj.as )
	end
	return s
end

-- replacing decimal separator and inserting group separators
function mu.formatNumber( num )
	if mu.isSet( mi.texts.decimalPoint ) and mi.texts.decimalPoint ~= '.' then
		num = num:gsub( '%.', mi.texts.decimalPoint )
	end

	if mu.isSet( mi.texts.groupSeparator ) then
		local count
		repeat
			num, count = num:gsub( '^([-+]?%d+)(%d%d%d)',
				'%1%' .. mi.texts.groupSeparator .. '%2' ) 
		until count == 0
	end
    return num
end

function mu.tableInsert( tab, value )
	if mu.isSet( value ) then
		table.insert( tab, value )
	end
end

-- splitting string s at sep, removing empty substrings
-- sep is a single character separator but no magic pattern character
function mu.textSplit( s, sep )
	local result = {}
	for str in s:gmatch( '([^' .. sep .. ']+)' ) do
		mu.tableInsert( result, mw.text.trim( str ) )
	end
	return result
end

local function encodeSpaces( s )
	s = s:gsub( ' ', '_' )
	return s
end

-- Splitting comma separated lists to a table and converting items
function mu.split( s )
	local arr = {}
	if not mu.isSet( s ) then
		return arr
	end
	for i, str in ipairs( mu.textSplit( s, ',' ) ) do
		arr[ encodeSpaces( str ) ] = 1
	end
	return arr
end

function mu.makeSpan( s, class, isBdi, attr, css )
	return tostring( mw.html.create( isBdi and 'bdi' or 'span' )
		:addClass( class )
		:attr( attr or {} )
		:css( css or {} )
		:wikitext( s )
	)
end

-- bdi and bdo tags are not working properly on all browsers. Adding marks
-- (lrm, rlm) is maybe the only way for a correct output
function mu.languageSpan( s, titleHint, page, country, addClass )
	if not mu.isSet( s ) then
		return ''
	end

	local bdi = mw.html.create( 'bdi' )
		:addClass( addClass )
		:wikitext( s )
	local c = country.lang
	if c == '' or c == page.lang then
		return tostring( bdi )
	end

	local dir
	local fStr = '%s'
	if country.isRTL and not page.isRTL then
		dir = 'rtl'
		fStr = '&rlm;%s&lrm;'
	elseif not country.isRTL and page.isRTL then
		dir = 'ltr'
		fStr = '&lrm;%s&rlm;'
	end	

	return fStr:format( tostring( bdi
		:addClass( 'voy-lang voy-lang-' .. c )
		:attr( { title = mw.ustring.format( titleHint , country.langName ),
			lang = c, dir = dir } )
	) )
end

function mu.addWdClass( isFromWikidata )
	return isFromWikidata and ' voy-wikidata-content' or ''
end

function mu.getAliases( tab, key )
	local result = {}
	if not tab then
		return result
	end
	local v
	for k, tb in pairs( tab ) do
		v = tb[ key ]
		if v then
			if type( v ) == 'table' then
				for i = 1, #v do
					result[ v[ i ] ] = k
				end
			else
				result[ v ] = k
			end
		end
	end
	return result
end

function mu.yesno( val )
	return mi.yesno[ val:ulower() ]
end

-- check functions

local function normalizeValues( args )
	for i, param in ipairs( mi.options.normalizeValues ) do
		if mu.isSet( args[ param ] ) then
			args[ param ] = args[ param ]:ulower():gsub( '[_%s]+', ' ' )
		end
	end
end

local function emphasize( s )
	return mw.ustring.format( mi.texts.emph, s )
end

-- args: template arguments consisting of argument name as key and a value
-- validKeys: table with argument name as key used by the script and
--    a string or a table of strings for argument names used by the local wiki
function mu.checkArguments( templateArgs, validKeys )
	local args = {}
	if not templateArgs or not validKeys or not next( validKeys ) then
		return args
	end

	local keys = {} -- list of wiki-dependent parameter names
	for key, params in pairs( validKeys ) do
		if type( params ) == 'string' then
			keys[ params ] = key
		else
			for i = 1, #params do
				keys[ params[ i ] ] = key
			end
		end
	end
	
	local targetKey
	for key, arg in pairs( templateArgs ) do
		targetKey = keys[ key ]
		if targetKey then
			if args[ targetKey ] then -- prevents duplicates
				table.insert( mu.duplicateAliases, emphasize( key ) )
			else
				args[ targetKey ] = arg
			end
		else
			table.insert( mu.invalidParams, emphasize( key ) )
		end
	end
	normalizeValues( args )
	return args
end

local function removeNS( s, nsTable )
	if not s:find( ':', 1, true ) then
		return s
	end

	local t = s
	for i = 1, #nsTable do
		t = mw.ustring.gsub( t, '^' .. nsTable[ i ] .. ':', '' )
		if s ~= t then
			return t
		end
	end
	return t
end

function mu.checkCommonsCategory( args )
	-- remove namespace from category
	if mu.isSet( args.commonscat )  then
		args.commonscat = removeNS( args.commonscat, mi.texts.CategoryNS )
	end
end

-- checking coordinates
function mu.checkCoordinates( args )
	local function clearCoordinates()
		args.lat, args.long = '', ''
	end

	local t
	if type( args.lat ) == 'boolean' or type( args.long ) == 'boolean' then
		clearCoordinates()
	end
	if args.lat == '' and args.long == '' then
		return
	elseif args.lat ~= '' and args.long == '' then
		t = args.lat:find( ',', 1, true )
		if t then
			args.long = mw.text.trim( args.lat:sub( t + 1, #args.lat ) )
			args.lat = mw.text.trim( args.lat:sub( 1, t - 1 ) )
		end
	end
	if args.lat == '' or args.long == '' then
		clearCoordinates()
		mu.addMaintenance( 'wrongCoord' )
		return
	end

	local dms = false
	t = tonumber( args.lat )
	if t then
		args.lat = math.abs( t ) <= 90 and t or ''
	else
		t = cd.toDec( args.lat, 'lat', 6 )
		args.lat = t.error == 0 and t.dec or ''
		dms = args.lat ~= ''
	end

	if args.lat ~= '' then
		t = tonumber( args.long )
		if t then
			args.long = ( t > -180 and t <= 180 ) and t or ''
		else
			t = cd.toDec( args.long, 'long', 6 )
			args.long = t.error == 0 and t.dec or ''
			dms = dms or args.long ~= ''
		end
	end

	if args.lat == '' or args.long == '' then
		clearCoordinates()
		mu.addMaintenance( 'wrongCoord' )
	elseif dms then
		mu.addMaintenance( 'dmsCoordinate' )
	end
end

-- check zoom level
function mu.checkZoom( args )
	args.zoom = math.floor( tonumber( args.zoom ) or mi.map.defaultZoomLevel )
	if args.zoom < 0 or args.zoom > mi.map.maxZoomLevel then
		args.zoom = mi.map.defaultZoomLevel
	end
end

-- image check
function mu.checkImage( args, entity )
	if type( args.image ) == 'boolean' or args.image == '' then
		return
	end

	-- formal checks
	if args.image:find( '^https?:' ) then
		args.image = ''
	else
		-- remove namespace
		args.image = removeNS( args.image, mi.texts.FileNS )
		local extensionExists = false
		local im = args.image:lower()
		for i = 1, #mi.fileExtensions do
			if im:find( '%.' .. mi.fileExtensions[ i ] .. '$' ) then
				extensionExists = true
				break
			end
		end
		if not extensionExists then
			args.image = ''
		end
	end
	if args.image == '' then
		mu.addMaintenance( 'wrongImgName' )
		return
	end

	local alreadyChecked = false
	if mi.options.mediaCheck and args.image ~= '' then
		if not mi.options.WDmediaCheck and entity then
			-- check if image is stored in Wikidata
			local imgs = wu.getValues( entity, mi.properties.image, nil )
			for i = 1, #imgs do
				if imgs[ i ] == args.image then
					alreadyChecked = true
					break
				end
			end
		end
		if not alreadyChecked then
			-- expensive function call
			local title = mw.title.new( 'Media:' .. args.image )
			if not title or not title.exists then
				mu.addMaintenance( 'missingImg', args.image )
				args.image = ''
			end
		end
	end
end

function mu.checkStatus( args )
	args.statusTable = {}
	local hash = {}
	if mu.isSet( args.status ) then
		local statusAliases = mu.getAliases( mi.statuses, 'alias' )
		for i, t in ipairs( mu.textSplit( args.status, ',' ) ) do
			t = encodeSpaces( t )
			t = statusAliases[ t ] or t
			if mi.statuses[ t ] then
				if not hash[ t ] then
					table.insert( args.statusTable, t )
					hash[ t ] = 'x'
				end
			else
				mu.addMaintenance( 'unknownStatus' )
			end
		end
	end
end

function mu.checkStyles( args )
	if mu.isSet( args.styles ) then
		if mi.nameStyles[ args.styles:lower() ] then
			args.styles = args.styles:lower()
			args.styleClass = ' listing-name-style-' .. args.styles
			args.styles = mi.nameStyles[ args.styles ]
		end
	else
		args.styles = nil
	end
end

-- groups translation for map legend into Wiki language
local function translateGroup( group )
	if not mu.isSet( group ) then
		group = mt.types.error.group
	end
	local t = mg.groups[ group ]
	if t then
		t = t.map or t.label or t.alias or group
		if type( t ) == 'table' then
			t = t[ 1 ]
		end
		return t
	end
	return group
end

local function getTypeFromTypeAliases( aType )
	if not mu.typeAliases then
		mu.typeAliases = mu.getAliases( mt.types, 'alias' )
	end
	return mu.typeAliases[ aType ]
end

local function getGroupFromGroupAliases( group )
	if not mu.groupAliases then
		mu.groupAliases = mu.getAliases( mg.groups, 'alias' )
	end
	return mu.groupAliases[ group ]
end

-- getting marker type and group
function mu.checkTypeAndGroup( args )
	local s, t
	args.typeTable = {}
	args.subtypeTable = {}

	-- check group
	if mu.isSet( args.group ) then
		mu.addMaintenance( 'groupUsed' )
		s = mg.groups[ args.group ]
		if not s then
			s = getGroupFromGroupAliases( args.group )
			if s then
				args.group = s
				s = mg.groups[ args.group ]
			end
		end
		if not s then			
			mu.addMaintenance( 'unknownGroup' )
			args.group = ''
		elseif s.is and s.is == 'color' and mi.options.useTypeCateg then
			mu.addMaintenance( 'group', args.group )
		end
	end

	-- check type
	if not mu.isSet( args.type ) then
		args.type = mt.types.error.group
		mu.addMaintenance( 'missingType' )
	elseif args.type == mt.types.error.group then
		mu.addMaintenance( 'unknownType', args.type )
	end
	if args.type == mt.types.error.group then
		args.group = mt.types.error.group
	else
		-- split seperate types and analyse them
		for i, t in ipairs( mu.textSplit( args.type, ',' ) ) do
			-- try to find the type t in types or groups
			t = encodeSpaces( t )
			if not mt.types[ t ] then
				t = getTypeFromTypeAliases( t ) or getGroupFromGroupAliases( t ) or t
			end

			s = mg.groups[ t ]
			if s then -- type is a group itself
				if s.is and s.is == 'color' then
					if mi.options.excludeColorTypes then
						args.group = mt.types.error.group
						mu.addMaintenance( 'unknownType', t )
					else
						mu.addMaintenance( 'typeIsColor' )
					end
				elseif not mi.options.noTypeMsgs then
					mu.addMaintenance( 'typeIsGroup' )
				end
				if args.group == '' then
					args.group = t
				end
			else
				s = mt.types[ t ]
				if s then
					if args.group == '' then
						args.group = s.group
					end
					if mu.isSet( s.subtype ) then
						table.insert( args.subtypeTable, t )
					end
				else
					args.group = mt.types.error.group
					mu.addMaintenance( 'unknownType', t )
				end
			end
			table.insert( args.typeTable, t )
			if mi.options.useTypeCateg then
				mu.addMaintenance( 'type', t )
			end
		end
		args.type = table.concat( args.typeTable, ',' )
	end
	args.groupTranslated = translateGroup( args.group )
	mu.getColor( args )
end

local function isUrl( url )
	uc = uc or require( 'Module:UrlCheck' )
	return uc.isUrl( url, mi.options.skipPathCheck )
end

-- url check in args
function mu.checkUrl( args )
	if not mu.isSet( args.url ) then
		return
	end

	local c = isUrl( args.url ) -- getting result code
	if c > 2 then
		mu.addMaintenance( 'wrongUrl' )
		args.url = ''
	elseif c == 2 then -- URL contains IP address
		mu.addMaintenance( 'urlWithIP' )
	else
		for i = 1, #mi.services do
			if args.url:find( mi.services[ i ].key .. '.com', 1, true ) then
				mu.addMaintenance( 'urlIsSocialMedia' )
				args.url = ''
			end
		end
	end
end

-- type and group functions
-- getting a set of parameters for a given type
function mu.getTypeParams( aType )
	return mt.types[ aType ]
end

function mu.idToType( id )
	if not mu.typeIds then
		mu.typeIds = mu.getAliases( mt.types, 'wd' ) -- Q id to type table
	end
	return mu.typeIds[ id ]
end

function mu.getTypeLabel( id )
	if not mu.isSet( id ) then
		return ''
	end
	if id:match( '^Q%d+$' ) then
		id = mu.idToType( id )
		if not id then
			return ''
		end
	else
		id = id:gsub( '[_%s]+', '_' )
	end

	local at, t
	t = mt.types[ id ]
	if not t then
		t = getTypeFromTypeAliases( id )
		t = t and mt.types[ t ]
	end
	if t then
		t = t.label or id
		at = t:find( ',' )
		if at then
			t = t:sub( 1, at - 1 )
		end
	else
		t = id:gsub( '_', ' ' )
	end
	return t
end

function mu.typeExists( aType )
	return mt.types[ aType ] and aType or getTypeFromTypeAliases( aType )
end

-- check if the specified group can have events
function mu.groupWithEvents( group )
	return mg.groups[ group ] and mg.groups[ group ].withEvents
end

-- see: https://www.w3.org/TR/WCAG20/#relativeluminancedef
local function hexToLinear( hex )
	hex = tonumber( hex, 16 ) / 255.0
	if hex <= 0.03928 then
		return hex / 12.92
	else
		return ( ( hex + 0.055 ) / 1.055 ) ^ 2.4
	end
end

-- relative luminance of a color
-- 6 digit hex color
local function hexToLuminance( color )
	local r = hexToLinear( color:sub( 1, 2 ) )
	local g = hexToLinear( color:sub( 3, 4 ) )
	local b = hexToLinear( color:sub( 5, 6 ) )

	return 0.2126 * r + 0.7152 * g + 0.0722 * b
end

local function isInverse( backgroundColor )
	-- magenta, error: 0.2848
	-- the threshold should not be greater than 0.4 
	local luminanceThreshold = hexToLuminance( 'FF00FF' )

	backgroundColor = backgroundColor:sub( 2 ) -- remove #
		:gsub( '^(%x)(%x)(%x)$', '%1%1%2%2%3%3' )
	local luminance = hexToLuminance( backgroundColor )

	return hexToLuminance( backgroundColor ) > luminanceThreshold
end

-- getting color from group in args
function mu.getColor( args )
	local c = mg.groups[ args.group ] or mg.groups[ 'error' ]
	args.color = c.color
	args.inverse = isInverse( c.color )
end

-- Splitting comma separated lists to a table of key items
-- checking items with allowed key values of validValues table
local function splitAndCheck( s, validValues )
	local values = {}
	if not validValues then
		return values, ''
	end

	local errors = {}
	for item, v in pairs( mu.split( s ) ) do
		-- value check
		if validValues[ item ] then
			values[ item ] = 1
		else
			table.insert( errors, item )
		end
	end
	return values, table.concat( errors, mu.commaSeparator )
end

local function setCopyMarker( args, show )
	if show.copy and ( mu.isSet( args.copyMarker ) or not mu.isSet( args.wikidata ) ) then
		show.copy = nil
		mu.addMaintenance( 'deleteShowCopy' )
	end
	if show.copy then
		args.copyMarker = args.wikidata
	end
	if mu.isSet( args.copyMarker ) then
		show.symbol = nil
	end
end

-- treatment of social media services if Wikidata is available
local function setSocialMedia( args, value )
	for i, service in ipairs( mi.services ) do
		args[ service.key ] = value
	end
end

function mu.getShow( default, args, validValues )
	local show = mu.split( default )
	local add, err = splitAndCheck( args.show, validValues )
	if err ~= '' then
		mu.addMaintenance( 'unknownShow', err )
	end
	-- maintenance
	if add.inline then
		show.inlineSelected = true
		mu.addMaintenance( 'showInlineUsed' )
	end
	if add.poi then
		mu.addMaintenance( 'showPoiUsed' ) -- is default
	end
	-- overwriting defaults
	if add.none or add.coord or add.poi or add.all then
		show.all, show.coord, show.poi = nil, nil, nil
	end
	-- merge show with add values
	for key, value in pairs( add ) do
		show[ key ] = value
	end
	if show.none then
		show.none, show.all, show.coord, show.poi = nil, nil, nil, nil
	end
	if show.all then
		show.all   = nil
		show.coord, show.poi = 1, 1
	end
	if show.noname then
		show.noname, show.name = nil, nil
	else
		show.name = 1
	end
	setCopyMarker( args, show )
	if args.wikidata ~= '' then
		if show.socialmedia then
			setSocialMedia( args, 'y' )
		elseif show.nosocialmedia then
			setSocialMedia( args, 'n' )
		end
	end
	if not show.nosocialmedia then
		show.socialmedia = 1
	end

	return show
end

local function replaceWithSpace( s, pattern )
	s = s:find( pattern ) and s:gsub( pattern, ' ' ) or s
	return s
end

-- removing line breaks and controls from parameter strings
function mu.removeCtrls( s, onlyInline )
	local descrDiv = false -- div tag instead of span tag for description needed?

	-- remove controls from tags before test
	s = s:gsub( '(<[^>]+>)', function( t )
			return replaceWithSpace( t, '[%z\1-\31]' )
		end )

	local t = replaceWithSpace( s, '</br%s*>' )
	if onlyInline then
		t = replaceWithSpace( t, '<br[^/>]*/*>' )
		t = replaceWithSpace( t, '</*p[^/>]*/*>' )
		t = replaceWithSpace( t, '[%z\1-\31]' )
		-- not %c because \127 is used for Mediawiki tags (strip markers `UNIQ)
	else
		t = replaceWithSpace( t, '[%z\1-\9\11\12\14-\31]' )
		descrDiv = t:find( '[\10\13]' ) or t:find( '<br[^/>]*/*>' ) or
			t:find( '<p[^/>]*>' )
	end
	if t ~= s then
		mu.addMaintenance( 'illegalCtrls' )
	end

	-- remove LTR and RTL marks
	t = mw.ustring.gsub( t, '[‎‏]+', '' )

	if descrDiv then
		-- unify line breaks to Linux mode
		t = t:gsub( '\13\10', '\10' ):gsub( '\13', '\10' )
		-- replace line breaks by <br> in block mode
		t = t:gsub( '([^>%]\10])\10+([^<%[\10])', '%1<br class="listing-next-paragraph" />%2' )
	end

	return replaceWithSpace( t, '%s%s+' ), descrDiv
end

-- fetch data from Wikidata
function mu.getCoordinatesFromWikidata( args, fromWikidata, entity )
	if not entity or ( args.lat ~= '' and args.long ~= '' ) then
		return
	end

	-- center coordinates from Wikidata
	local c = wu.getValue( entity, mi.properties.centerCoordinates )
	if c == '' then
		-- coordinates from Wikidata
		c = wu.getValue( entity, mi.properties.coordinates )
	end
	if c ~= '' then
		args.lat, args.long = c.latitude, c.longitude
		fromWikidata.lat = true
	end
end

local function typeSearch( p31 )
	-- p31: array of Wikidata values
	if not p31 or #p31 == 0 then
		return ''
	end

	local firstStep = true

	local function compareLabels( ar )
		if not ar then
			return nil
		elseif type( ar ) == 'string' then
			ar = { ar }
		end
		for i, value in ipairs( ar ) do
			if mu.isSet( value ) then
				value = ( encodeSpaces( value ) )
				if mt.types[ value ] then
					return value
				end
			end
		end
		return nil
	end

	local function compareIds( ar )
		local id, t
		for i = 1, #ar do
			id = ar[ i ].id
			-- md: indexed array of q id - types relations
			t = mu.idToType( id )
			if t then
				return t
			end

			-- checking English label and aliases
			t = compareLabels( mw.wikibase.getLabelByLang( id, 'en' ) )
				or compareLabels( wu.getAliases( id, 'en' ) )
			if t then
				if firstStep and not mi.options.noTypeMsgs then
					firstStep = false
					mu.addMaintenance( 'typeFromWDchain' )
				end
				return t
			end
		end
		return nil
	end

	local aType = compareIds( p31 ) -- check p31 ids first, maybe step 2 is not nessary
	if aType then
		return aType
	end

	-- now functions becomes expensive because of multiple wu.getValues calls
	local id, ids
	firstStep = false
	for i = 1, #p31 do -- step 2: analyse P279 chains of first ids
		id = p31[ i ].id -- start id
		local j = 0
		repeat
			ids = wu.getValues( id, mi.properties.subclassOf, nil )
			if #ids > 0 then
				aType = compareIds( ids )
				if aType then
					if not mi.options.noTypeMsgs then
						mu.addMaintenance( 'typeFromWDchain' )
					end
					return aType
				end
				id = ids[ 1 ].id
			end
			j = j + 1

		-- limit: maximum levels to analyse
		until j >= mi.options.searchLimit or #ids == 0
	end

	return ''
end

function mu.getTypeFromWikidata( args, entity )
	if mu.isSet( args.type ) then
		return
	end

	local p31 = wu.getValues( entity, mi.properties.instanceOf )
	if #p31 == 0 then
		p31 = wu.getValues( entity, mi.properties.subclassOf )
	end
	args.type = typeSearch( p31 )
end

function mu.getCommonsCategory( args, entity )
	-- getting commonscat from commonswiki sitelink before P373
	-- because sitelink is checked by Wikidata
	if type( args.commonscat ) == 'boolean' then
		args.commonscat = ''
	end

	local t = wu.getSitelink( entity, 'commonswiki' ) or ''
	if t:match( '^Category:.+$' ) then
		t = t:gsub( '^[Cc]ategory:', '' )
	else
		t = wu.getValue( entity, mi.properties.commonsCategory )
		if t == '' then
			local id = wu.getId( entity, mi.properties.mainCategory )
			if id ~= '' then
				t = wu.getSitelink( id, 'commonswiki' ) or ''
				t = t:gsub( '^[Cc]ategory:', '' )
			end
		end
	end
	if t ~= '' and args.commonscat ~= '' then
		mu.addMaintenance( 'commonscatWD' )
	end
	if args.commonscat == '' then
		args.commonscat = t
	end
end

function mu.getLangTable( wikiLang, localLang )
	local langs = { wikiLang }
	for i, lang in ipairs( mi.langs ) do
		table.insert( langs, lang )
	end
	if mu.isSet( localLang ) and localLang ~= wikiLang then
		table.insert( langs, localLang )
	end
	return langs
end

-- getting names from Wikidata
function mu.getNamesFromWikidata( args, fromWikidata, page, country, entity )
	-- getting official names
	local officialNames =
		wu.getMonolingualValues( entity, mi.properties.officialName )

	if type( args.name ) == 'boolean' or args.name == '' then
		args.name = ''
		local langs = mu.getLangTable( page.lang )
		for i, lang in ipairs( langs ) do
			args.name = officialNames[ lang ]
			if args.name then
				break
			end
		end
		-- if failed then get labels
		if not mu.isSet( args.name ) then
			for i, lang in ipairs( langs ) do
				args.name = wu.getLabel( entity, lang, true ) -- no fallback
				if args.name then
					break
				end
			end
			args.name = args.name or ''
		end
		if args.name ~= '' then
			mu.addMaintenance( 'nameFromWD' )
			fromWikidata.name = true
		end
	end

	-- get local name if no name is available
	if args.name == '' and
		not ( type( args.nameLocal ) == 'string' and args.nameLocal ~= '' ) then
		args.nameLocal = true

	-- no local name if country and wiki language are identical
	elseif args.nameLocal == true and page.lang == country.lang then
		args.nameLocal = ''	
	end

	if type( args.nameLocal ) == 'string' and args.nameLocal ~= '' then
		-- keeping local name in any case for html data
		args.addNameLocal = args.nameLocal
		return
	end

	local nameLocal = officialNames[ country.lang ] or ''
	if nameLocal == '' then
		nameLocal = wu.getLabel( entity, country.lang, true ) or ''
	end

	if args.nameLocal == true then
		args.nameLocal = nameLocal
		if args.name == '' and args.nameLocal ~= '' then
			args.name = args.nameLocal
			args.nameLocal = ''
			mu.addMaintenance( 'nameFromWD' )
			fromWikidata.name = true
		end
		if args.name:ulower() == args.nameLocal:ulower() then
			args.nameLocal = ''
		end
		fromWikidata.nameLocal = args.nameLocal ~= ''
	elseif page.lang ~= country.lang and args.name ~= nameLocal then
		args.addNameLocal = nameLocal
	end
end

-- getting link to Wikivoyage
function mu.getArticleLink( args, entity, page )
	local title, isRedirect =
		wu.getCheckedSitelink( entity, page.lang .. page.globalProject )
	if title and title ~= page.text then
		args.wikiPage = title
		if isRedirect == true then
			args.linkIsRedirect = true
		end
	end
end

-- marker functions
-- returns a single data set from Module:Marker utilities/Maki icons
local function getMaki( key )
	mm = mm or require( 'Module:Marker utilities/Maki icons' )
	key = key:lower():gsub( '[_%s]+', '-' )
	return mm.icons[ key ]
end

local function getMakiIconId( key )
	if not mu.isSet( key ) then
		return nil
	end
	key = key:lower():gsub( '[_%s]+', '-' )
	if getMaki( key ) then
		return key
	end
	key = mt.types[ key ] and mt.types[ key ].icon
	if key and getMaki( key ) then
		return key
	end
	return nil
end

local function addIconToMarker( args )
	args.text = ( '[[File:Maki7-%s.svg|x14px|link=|class=noviewer]]' ):format( args.symbol )
	args.isIcon = true
end

-- distinguishing marker symbols, default: number
local function makeMarkerProperties( args, show )
	args.symbol = args.symbol or ''
	local noSymbol = args.symbol == ''
	if args.symbol == '' and show.poi and show.symbol then
		args.symbol = getMakiIconId( args.typeTable[ 1 ] ) or ''
	end
	local isIcon = getMaki( args.symbol )
	if args.symbol == '' or args.symbol == 'number' then
		args.symbol = '-number-' .. args.group
	elseif args.symbol == 'letter' then
		args.symbol = '-letter-' .. args.group
	elseif args.symbol:len() == 1 and args.symbol:match( '%w' ) then
		args.text = args.symbol:upper()
		mu.addMaintenance( 'numberUsed' )
	elseif args.symbol ~= '' and args.text == '' and isIcon then
		addIconToMarker( args )
	elseif args.symbol ~= '' and not isIcon then
		args.text = mi.texts.closeX
		args.isIcon = true
		args.group = 'error'
		mu.getColor( args )
		mu.addMaintenance( noSymbol and 'unknownMAKI' or 'unknownIcon' )
	end
end

-- making marker symbol
function mu.makeMarkerSymbol( args, show, frame )
	local extraClasses = args.inverse and ' listing-map-inverse' or
		' listing-map-not-inverse'
	if args.group == 'error' then
		extraClasses = extraClasses .. ' listing-map-is-error'
	end
	local title = args.givenName.all

	if mu.isSet( args.copyMarker ) then
		local copyClass = 'listing-map plainlinks printNoLink voy-copy-marker'
			.. extraClasses
		return tostring( mw.html.create( 'span' )
			:addClass( copyClass )
			:css( colorAdjust )
			-- display will be replaced by [[MediaWiki:Gadget-GeneralChanges.js]] script
			:css( { display = 'none' } )
			:attr( { ['data-copy-marker-attribute'] = args.copyMarker:match( 'Q%d+' )
				and 'data-wikidata' or 'data-name', title = mi.texts.tooltip,
				['data-copy-marker-content'] = args.copyMarker } )
		)
	end
	
	makeMarkerProperties( args, show )
	if args.isIcon then
		extraClasses = extraClasses .. ' listing-map-is-symbol'
	end
	local lon = tonumber( args.long )
	local lat = tonumber( args.lat )
    local tagArgs = {
        zoom = tonumber( args.zoom ),
        latitude = lat,
        longitude = lon,
        show = mg.showAll,
    }
	if mu.isSet( args.text ) then
		tagArgs.text = args.text
		tagArgs.class = 'no-icon'
	end
	if mu.isSet( args.mapGroup ) then
		tagArgs.group, tagArgs.show = args.mapGroup, args.mapGroup
	else
		tagArgs.group = args.groupTranslated
	end
	if mu.isSet( args.image ) then
		tagArgs.description = '[[File:' .. args.image .. '|100x100px|' .. title .. ']]'
	end

	local geoJson = {
		type = 'Feature',
		geometry = {
			type = 'Point',
			coordinates = { lon, lat }
		},
		properties = {
			title = title,
			description = tagArgs.description,
			['marker-symbol'] = args.symbol,
			['marker-color'] = args.color,
			['marker-size'] = 'medium',
		}
	}

	return tostring( mw.html.create( 'span' )
		:addClass( 'listing-map plainlinks printNoLink' .. extraClasses )
		:attr( 'title', mi.texts.tooltip )
		:css( colorAdjust )
		:css( { ['background-color'] = args.color,
			color = args.inverse and '#000' or '#fff' } )
		-- frame:extensionTag is expensive
		:wikitext( frame:extensionTag( 'maplink', mw.text.jsonEncode( geoJson ), tagArgs ) )
	)
end

-- icon functions
function mu.makeStatusIcons( args )
	local result = ''
	for i, v in ipairs( args.statusTable ) do
		result = result .. mu.makeSpan( ' ', 'listing-status listing-status-' .. v,
			false, { title = mi.statuses[ v ].label }, colorAdjust )
		if mi.statuses[ v ].category then
			result = result .. ( '[[Category:%s]]' ):format( mi.statuses[ v ].label )
		end
	end
	return result
end

function mu.addLinkIcon( classes, link, title, text, addSpace )
	local span = mu.makeSpan( ' ', nil, false, { title = title, ['data-icon'] = text },
		colorAdjust ) -- space to keep the span tag
	local lFormat = ( link:find( '^https?://' ) or link:find( '^//' ) )
		and '[%s %s]' or '[[%s|%s]]'
	local iconLink = mw.ustring.format( lFormat, link, span )
	if addSpace then
		iconLink = mu.makeSpan( '​ ', 'listing-icon-with-space', true ) .. iconLink
	end
	return mu.makeSpan( iconLink, 'listing-icon ' .. classes )
end

-- adding linked sister icons
local function addSisterIcons( icons, sisters, name, id )
	local icon
	for i, key in ipairs( { 'wikivoyage', 'wikipedia', 'commons', 'wikidata' } ) do
		if mu.isSet( sisters[ key ] ) then
			icon = mu.addLinkIcon( 'listing-sister-icon listing-sister-' .. key, sisters[ key ],
				mw.ustring.format( mi.iconTitles[ key ], name, id ), key,
				key == 'wikidata' ) -- add leading space
			table.insert( icons, icon )
		end
	end

	-- return true if only Wikidata icon
	return mu.isSet( sisters.wikidata ) and #icons == 1
end

-- getting sister project links
local function getWikiLink( langArray, wiki, entity, wikilang )
	local prefix = wiki == 'wiki' and 'w:' or 'voy:'
	local link
	for i, lang in ipairs( langArray ) do
		if lang ~= '' then
			link = wu.getFilteredSitelink( entity, lang .. wiki )
			if link then
				prefix = prefix .. ( lang ~= wikilang and ( lang .. ':' ) or '' )
				return prefix .. link
			end
		end
	end
	return ''
end

-- adding Wikimedia sister project icons
local function makeSisterIcons( icons, args, page, country, entity )
	local sisters = {
		commons    = '', -- link to Commons category
		wikidata   = '', -- link to Wikidata
		wikipedia  = '', -- link to Wikipedia
		wikivoyage = ''  -- link to another branch, usually en, as a sister link
	}

	if mu.isSet( args.wikipedia ) then
		sisters.wikipedia = 'w:' .. args.wikipedia
	end
	if mu.isSet( args.wikidata ) then
		local langs = mu.getLangTable( page.lang, country.lang )

		if sisters.wikipedia == '' then
			sisters.wikipedia = getWikiLink( langs, 'wiki', entity, page.lang )
		end
		if args.wikiPage == '' then
			table.remove( langs, 1 ) -- exclude page.lang
			sisters.wikivoyage = getWikiLink( langs, page.globalProject, entity, page.lang )
			if sisters.wikivoyage ~= '' then
				mu.addMaintenance( 'linkToOtherWV' )
			end
		end
		sisters.wikidata = 'd:' .. args.wikidata
	end
	if args.commonscat ~= '' then
		sisters.commons = 'c:Category:' .. args.commonscat
	end

	return addSisterIcons( icons, sisters, args.givenName.name, args.wikidata )
end

-- creating social media icons including value check
local function makeSocial( icons, args, fromWikidata, name )
	local domain, span, t, which

	for i, service in ipairs( mi.services ) do
		-- check values first
		t = args[ service.key ] or ''
		domain = type( service.url ) == 'string' and service.url or service.url[ 1 ]
		domain = domain:gsub( 'com/.*', 'com/' )
		if t ~= '' then
			if t:match( '^http' ) then
				if not t:find( 'https', 1, true ) then
					t = t:gsub( '^http', 'https' )
					mu.addMaintenance( 'wrongSocialUrl', service.key )
				end
				if isUrl( t ) > 1 or
					not t:match( '^' .. domain .. '.+$' ) then
					t = ''
					mu.addMaintenance( 'wrongSocialUrl', service.key )
				end
				if t ~= '' and not fromWikidata[ service.key ] then
					mu.addMaintenance( 'socialUrlUsed', service.key )
				end
			else
				local match = false
				local sp = service.pattern
				if type( sp ) == 'string' then
					sp = { sp }
				end
				for i = 1, #sp do
					if mw.ustring.match( t, sp[ i ] ) then
						match = true
						which = i
						break
					end
				end
				if not match then
					t = ''
					mu.addMaintenance( 'wrongSocialId', service.key )
				end
			end
		end
		args[ service.key ] = t

		-- create symbol link
		if t ~= '' then
			if not t:find( domain, 1, true ) then
				-- multiple service urls
				local formatStr = type( service.url ) == 'string' and service.url or
					service.url[ which ]
				t = mw.ustring.format( formatStr, t )				
			end

			span = mu.addLinkIcon( 'listing-social-media listing-social-media-' ..
				service.key .. mu.addWdClass( fromWikidata[ service.key ] ), t,
				mw.ustring.format( mi.iconTitles[ service.key ], name ), service.key )
			table.insert( icons, span )
		end
	end
end

function mu.makeIcons( args, page, country, entity, show, fromWikidata )
	local icons = {}
	local onlyWikidata = mi.options.showSisters and not show.nositelinks and
		makeSisterIcons( icons, args, page, country, entity )
	if show.socialmedia then
		makeSocial( icons, args, fromWikidata, args.givenName.name )
	end
	if #icons > 0 then
		return ( onlyWikidata and '' or mi.texts.space ) .. table.concat( icons, '' )
	else
		return ''
	end
end

-- output functions
local function removeStars( args )
	for i, param in ipairs( mi.options.noStarParams ) do
		if mu.isSet( args[ param ] ) and args[ param ]:find( '*', 1, true ) then
			args[ param ] = args[ param ]:gsub( '%*+', '' )
			args[ param ] = mw.text.trim( args[ param ] )
			mu.addMaintenance( 'nameWithStar' )
		end
	end
end

local function makeAnchorId( name )
	if name and not name:match( '^Q%d+$' ) then
		name = mw.uri.anchorEncode( name ):gsub( '&amp;', '&' ):gsub( '&#039;', "'" )
	end
	return mw.ustring.format( mi.texts.anchor, name )
end

-- getting name table with linked and unlinked names
local function getName( name, pageTitle )
	local r = {
		all = '', -- name or linked name
		name = '', -- only name
		pageTitle = '', -- if pageTitle ~= '' name is already linked
	}
	if type( name ) ~= 'string' or name == '' then
		return r
	end

	local s
	local t, c = mw.ustring.gsub( name, '^(.*)%[%[(.*)%]%](.*)$', '%2' )
	if c > 0 then -- is / contains [[...]]
		t = mw.text.trim( t )
		r.all = '[[' .. t .. ']]'
		s, c = mw.ustring.gsub( t, '^(.*)|(.*)$', '%2' )
		if c > 0 then -- is [[...|...]]
			r.name = mw.text.trim( s )
			r.pageTitle = mw.ustring.gsub( t, '^(.*)|(.*)$', '%1' )
			r.pageTitle = mw.text.trim( r.pageTitle )
		else
			r.name = t
			r.pageTitle = t
		end
	else
		r.name = name
		r.all = name
		if mu.isSet( pageTitle ) then
			r.pageTitle = pageTitle
			r.all = '[[' .. pageTitle .. '|' .. name .. ']]'
			r.fromWD = true
		end
	end
	removeStars( r, { 'name', 'all' } )
	return r
end

-- create givenName, displayName tables
function mu.prepareNames( args )
	local function simplifyString( s, length )
		s = mw.ustring.sub( s, 1, length )
		s = mw.ustring.gsub( s, "[%.'" .. '"“”„‟«»‘’‚‛‹›]', '' )
		s = mw.ustring.gsub( s, '[-‐‑‒–—―]', ' ' )
		return s:ulower():gsub( '%s%s+', ' ' )
	end

	local function removeDuplicate( value1, key2 )
		if not mu.isSet( value1 ) or not mu.isSet( args[ key2 ] ) then
			return
		end
		local minLen = math.min( mw.ustring.len( value1 ), mw.ustring.len( args[ key2 ] ) )
		if simplifyString( value1, minLen ) == simplifyString( args[ key2 ], minLen ) then
			args[ key2 ] = ''
		end
	end

	-- use local name if name is not given
	if args.name == '' and args.nameLocal ~= '' then
		args.name = args.nameLocal
		args.nameLocal = ''
	end
	if args.name == args.nameMap then
		args.nameMap = ''
	end
	-- missing name
	if args.name == '' then
		args.name = mi.texts.missingName
		mu.addMaintenance( 'missingNameMsg' )
	end
	-- names shall not contain tags or template calls
	if args.name:find( '<', 1, true ) or args.name:find( '{{', 1, true ) or
		args.nameMap:find( '<', 1, true ) or args.nameMap:find( '{{', 1, true ) or
		args.nameLocal:find( '<', 1, true ) or args.nameLocal:find( '{{', 1, true ) then
		mu.addMaintenance( 'malformedName' )
	end
	removeStars( args )
	-- handling linked names like [[article|text]]
	args.displayName = getName( args.name, args.wikiPage )
	if mu.isSet( args.nameMap ) then
		args.givenName = getName( args.nameMap, args.wikiPage )
	else
		-- real copy
		args.givenName = {}
		args.givenName.all = args.displayName.all
		args.givenName.name = args.displayName.name
		args.givenName.pageTitle = args.displayName.pageTitle
	end
	-- reset args.linkIsRedirect if Wikidata link is not used
	-- args.linkIsRedirect is set by mu.getArticleLink()
	if not args.displayName.fromWD then
		args.linkIsRedirect = nil
	end

	-- remove identical names
	removeDuplicate( args.givenName.name, 'nameLocal' )
	removeDuplicate( args.givenName.name, 'alt' )
	removeDuplicate( args.givenName.name, 'comment' )
	removeDuplicate( args.nameLocal, 'alt' )
	removeDuplicate( args.alt, 'comment' )
	removeDuplicate( args.directions, 'directionsLocal' )
	removeDuplicate( args.address, 'addressLocal' )
end

local function _makeAirport( code, args )
	local span = mu.makeSpan( args[ code ],
		'listing-' .. code .. '-code' .. mu.addWdClass( true ) )
	return mu.makeSpan( mw.ustring.format( mi.texts[ code ], span ),
		'listing-airport listing-' .. code )
end

local function makeAirport( args, show )
	if show.noairport then
		return ''
	else
		local t = args.type:gsub( ',.*$', '' ) -- only first type
		if mt.types[ t ] and not mt.types[ t ].useIATA then
			return ''
		end
	end

	if mu.isSet( args.iata ) then
		return _makeAirport( 'iata', args )
	elseif mu.isSet( args.icao ) then
		return _makeAirport( 'icao', args )
	else
		return ''
	end
end

-- creating a list of nick names etc.
local function makeNameAffix( args, page, country, addClass, show )
	local result = {}
	if mi.options.showLocalData then
		if mu.isSet( args.nameLocal ) then
			table.insert( result, 
				mu.languageSpan( args.nameLocal, mi.texts.hintName, page, country,
					'listing-name-local' .. addClass ) )
		end
		if mu.isSet( args.nameLatin ) then
			table.insert( result, mu.makeSpan( args.nameLatin,
				'listing-name-latin', false, { title = mi.texts.hintLatin,
					lang = mu.isSet( country.lang ) and ( country.lang .. '-Latn' ) or nil } ) )
		end
	end
	for i, key in ipairs( { 'alt', 'comment' } ) do
		if mu.isSet( args[ key ] ) then
			table.insert( result, mu.makeSpan( args[ key ], 'listing-' .. key ) )
		end
	end
	mu.tableInsert( result, makeAirport( args, show ) )
	if #result == 0 then
		return ''
	end
	return mu.makeSpan( mu.parentheses( table.concat( result, mu.comma ) ),
		'p-nickname nickname listing-add-names' )
end

-- replace brackets in names
local function replaceBrackets( s )
	s = s:gsub( '%[', '&#x005B;' ):gsub( '%]', '&#x005D;' )
	return s
end

-- creating (linked) name and its affix
function mu.makeName( result, args, show, page, country, nameClass, localClass )
	local s = ''
	local r = {}

	nameClass = nameClass ..
		( args.displayName.fromWD and ' listing-link-from-wd' or '' )

	-- clear redirect wiki link if required
	if args.linkIsRedirect and not show.wikilink then
		args.displayName.pageTitle = ''
		args.displayName.all = args.displayName.name
		args.linkIsRedirect = nil

		nameClass = nameClass .. ' listing-unused-redirect'
		mu.addMaintenance( 'unusedRedirect' )
	end
	if args.linkIsRedirect then
		nameClass = nameClass .. ' mw-redirect listing-link-is-redirect'
		mu.addMaintenance( 'linkIsRedirect' )
	end
	if mu.isSet( args.url ) and args.displayName.pageTitle == '' then
		s = '[' .. args.url .. ' '
			.. replaceBrackets( args.displayName.name ) .. ']'
	else
		s = args.displayName.all
	end
	if mu.isSet( args.nameExtra ) then
		s = s .. ' ' .. args.nameExtra
	end

	-- insert name
	if s ~= '' then
		local attr = { style = args.styles }
		if mu.isSet( args.id ) and mu.isSet( args.wikidata ) and
			not mu.isSet( args.copyMarker ) then
			attr.id = makeAnchorId( args.id )
		end
		s = mu.makeSpan( s,	'p-name fn org listing-name' .. nameClass ..
			( args.styleClass and args.styleClass or '' ), true, attr )
		if mu.isSet( args.metadata ) then
			s = s .. args.metadata
		end
		table.insert( r, s )
	end

	-- insert separate url icon
	if mu.isSet( args.url ) and args.displayName.pageTitle ~= '' then
		-- both article and web links
		table.insert( r, mu.addLinkIcon( 'listing-url', args.url,
			mi.iconTitles.internet, 'internet' ) )
	end

	-- insert name affix
	mu.tableInsert( r, makeNameAffix( args, page, country, localClass, show ) )
	
	if #r > 0 then
		table.insert( result, table.concat( r, mi.texts.space ) )
	end
end

function mu.parentheses( s, trim )
	if not mu.isSet( s ) then
		return ''
	end

	local formatter = mi.texts.parentheses
	if trim then
		formatter = mw.text.trim( formatter )
	end
	return mw.ustring.format( formatter, s )
end

-- getting DMS coordinates
function mu.dmsCoordinates( lat, long, name, fromWD, extraParams, noBrackets )
	local function coordSpan( title, text )
		return mu.makeSpan( text, 'coordStyle', false, { title = title } )
	end

	local dec
	local latDMS, dec, latMs = cd.getDMSString( lat, 4, 'lat', '', '', mi.map.defaultDmsFormat )
	local longDMS, dec, longMs = cd.getDMSString( long, 4, 'long', '', '', mi.map.defaultDmsFormat )

	local r = '[' .. mi.map.coordURL .. latMs .. '_' .. longMs .. '_' ..
		mw.uri.encode( extraParams ) .. '&locname=' .. mw.uri.encode( name ) ..
		' ' .. coordSpan( mi.texts.latitude, latDMS ) ..
		' ' .. coordSpan( mi.texts.longitude, longDMS ) .. ']'
	r = noBrackets and r or mu.parentheses( r )
	return mu.makeSpan( r, 'listing-dms-coordinates printNoLink plainlinks'
		.. mu.addWdClass( fromWD ) )
end

-- prepare value s for data attribute and replace all entities by characters
local function data( s )
	return mu.isSet( s ) and mw.text.decode( s, true ) or nil
end

-- adding wrapper and microformats
function mu.makeWrapper( result, args, page, country, show, list, frame )
	if not mu.isSet( args.nameLocal ) and mu.isSet( args.addNameLocal ) then
		args.nameLocal = args.addNameLocal
	end
	if not mu.isSet( args.addressLocal ) and mu.isSet( args.addAddressLocal ) then
		args.addressLocal = args.addAddressLocal
	end
	local wrapper = mw.html.create( show.inline and 'span' or 'div' )
		:addClass( 'vcard h-card ' .. args.template )
	if args.noGpx then
		wrapper:addClass( 'listing-no-gpx' )
	end
	if show.outdent and not show.inline then
		wrapper:addClass( 'listing-outdent' )
	end
	if show.inlineSelected or args.template == 'Marker' then
		wrapper:addClass( 'listing-inline' )
	end
	if mu.isSet( args.copyMarker ) or not show.poi then
		wrapper:addClass( 'voy-without-marker' )
	end
	if #args.statusTable > 0 then
		wrapper:addClass( 'listing-with-status' )
	end
	if args.givenName.name ~= mi.texts.missingName then
		wrapper:attr( 'data-name', data( args.givenName.name ) )
	end
	if not ( mu.isSet( args.copyMarker ) and args.copyMarker == args.wikidata ) then
		local id = mu.isSet( args.wikidata ) and args.wikidata or args.id
		if mu.isSet( id ) then
			wrapper:attr( 'id', makeAnchorId( id ) )
		end
	end
	local editClass = 'listing-edit'
	if mu.isSet( args.sectionFrom ) then
		args.sectionFrom = args.sectionFrom:gsub( '[_]+', ' ' )
		if args.sectionFrom ~= page.text then
			editClass = 'listing-no-edit'
		end
	end
	wrapper:addClass( editClass )
	wrapper:attr( 'data-location', data( page.subpageText ) )
		:attr( 'data-location-qid', page.entityId )
		:attr( 'data-wikilang', page.lang )
		:attr( 'data-country', data( country.iso_3166 ) )
		:attr( 'data-country-name', data( country.country ) )
		:attr( 'data-lang', data( country.lang ) )
		:attr( 'data-lang-name', data( country.langName ) )
		:attr( 'data-country-calling-code', data( country.cc ) )
		:attr( 'data-trunk-prefix', data( country.trunkPrefix ) )
		:attr( 'data-dir', data( country.isRTL and 'rtl' or 'ltr' ) )
		:attr( 'data-wiki-dir', data( page.isRTL and 'rtl' or 'ltr' ) )
		:attr( 'data-currency', data( country.addCurrency ) )
	local arg
	for key, value in pairs( list ) do
		if mu.isSet( args[ key ] ) then
			arg = args[ key ]:gsub( '<[^<>]*>', '' ) -- remove html tags
			wrapper:attr( value, data( arg ) )
		end
	end
	if not show.noCoord then
		wrapper:node( mw.html.create( 'span' )
			:addClass( 'p-geo geo listing-coordinates' )
			:css( 'display', 'none' )
			:node( mw.html.create( 'span' )
				:addClass( 'p-latitude latitude' )
				:wikitext( args.lat )
			)
			:node( mw.html.create( 'span' )
				:addClass( 'p-longitude longitude' )
				:wikitext( args.long )
			)	
		)
	end
	if not show.name then
		wrapper:node( mw.html.create( 'span' )
			:addClass( 'p-name fn org listing-name' )
			:css( 'display', 'none' )
			:wikitext( args.givenName.name )
		)
	end

	wrapper = tostring( wrapper:wikitext( result ) )

	-- adding coordinates to Mediawiki database
	-- frame:callParserFunction is expensive
	if not show.noCoord and mi.options.secondaryCoords then
		wrapper = wrapper .. frame:callParserFunction{ name = '#coordinates',
			args = { args.lat, args.long, country.extra, name = args.givenName.name } }
	end

	return wrapper
end

function mu.getPageData()
	local page = mw.title.getCurrentTitle()
	page.langObj = mw.getContentLanguage()
	page.lang = page.langObj:getCode()
	page.langName = mw.language.fetchLanguageName( page.lang, page.lang )
	page.isRTL = page.langObj:isRTL()
	page.entityId = mw.wikibase.getEntityIdForCurrentPage() -- can be nil
	page.siteName = mw.site.siteName
	page.globalProject = page.siteName:lower()
	if page.globalProject == 'wikipedia' then
		page.globalProject = 'wiki'
	end

	return page
end

return mu