コンテンツにスキップ

モジュール:Marker utilities

提供:ウィキボヤージュ
モジュールの解説[表示] [編集] [履歴] [キャッシュを破棄]

このモジュールは、モジュール:VCardで使用される関数を提供します。Wikivoyage:Marker utilities (ドイツ語版も参照。

ウィキデータでのバージョン: 2024-11-11 問題あり

使用状況

このモジュールを使用しているモジュールは以下の通りです:

関数

メンテナンス関数

function mu.initMaintenance( name )

この関数は、エラーメッセージ出力のための変数を初期化します。nameには、名前空間名を除いた関連モジュールの名前が入ります。また、この関数は不正なパラメータとエラー・注釈のテーブルも初期化します。

function mu.addMaintenance( key, value )

この関数は、keyに与えられたコードから、エラーや注釈などのメッセージを取得してエラーと注釈のテーブルに入れます。valueはメッセージの引数として、%sを置換します。上で紹介したいくつかの関数もこのテーブルに値を入れます。

function mu.getMaintenance()

この関数は、全てのエラーと注釈のメッセージを返します。

function mu.getCategories( formatStr )

使用されたすべてのウィキデータプロパティについて、そのプロパティのカテゴリへのリンクを返します。

補助関数

function mu.isSet( arg )

引数argがセットされているかどうかについて、trueまたはfalseを返します。argに空文字列以外の値が代入されている場合のみ、trueが返ります。

function mu.convertForSort( s )

ソート用に、文字列sに含まれる特殊文字を置換します。置換される文字は、国際化ファイルsubstitutesテーブルに入っています。漢字は量が膨大であり列挙が不可能であるため、日本語版ウィキボヤージュではこの関数に意味がありません。

function mu.formatNumber( num )

数値の引数numについて、小数点を文字列として置換し、3桁ごとに桁の区切り文字を挿入します。

function mu.tableInsert( tab, value )

テーブルtabが定義されており、空文字列でなければ、tabvalueを挿入します。

function mu.textSplit( s, sep )

文字列sを区切り文字sepで分割し、空文字列を含まないテーブルに代入します。sepは正確に1文字(1バイト)の長さを持ち、パターンマッチの構文は使用できません。この関数はmw.text.split()よりも遥かに速いです。

function mu.split( s )

文字列sをカンマで区切った連想配列を返します。部分文字列は小文字に変換され、スペースはアンダースコアで置換されます。

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

s<span>タグまたは<bdi>タグで囲んだ文字列を返します。classCSSクラス、attrは属性のテーブル、cssは要素に直接書かれるスタイルシートのテーブルです。

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

言語とテキストの方向が指定された<span>タグにテキストsを挿入します。titleHint<span>タグのtitle属性、argsはテンプレートのパラメータが入ったテーブル、countryは国の情報のテーブルです。

function mu.addWdClass( isFromWikidata )

isFromWikidatatrueの場合、クラス識別子wikidata-contentを返し、そうでなければ空文字列を返します。

function mu.getAliases( tab, key )

テーブルtabから、別名のキーと正式名称の値を持ったテーブルを作成します。

function mu.yesno( val )

valが既知の値であればyまたはnを返し、なければnilを返します。モジュール:Yesnoと同じ機能です。

評価関数

function mu.checkArguments( templateArgs, validKeys )

テンプレートの呼び出し時に、未知のパラメータや別名によって重複したパラメータが使用されているか確認します。これらの不明または重複したパラメータは、無効なパラメータのテーブル(invalidParams)または重複した別名のテーブル(duplicateAliases)に入ります。templateArgsは確認したいパラメータのテーブルで、validKeysは既知のパラメータのテーブルです(モジュール:VCard/i18nなどを参照)。返り値は有効なパラメータのテーブルです。

function mu.checkCommonsCategory( args )

この関数は、コモンズのカテゴリargs.commonscatから名前空間名を除去し、コモンズのカテゴリが設定されている場合はメンテナンス用のカテゴリを追加します。

function mu.checkCoordinates( lat, long )

latlongの値を確認します。エラーがある場合、latとlongは空文字列となります。エラーメッセージのテーブルにも対応する部分があります(下記参照)。

function mu.checkZoom( args )

args.zoomの値が0~19の範囲の数字でない場合、args.zoomを既定値に設定します。

function mu.checkImage( image, entity )

画像の値を確認し、エラーがあった場合には空文字列を代入します。エラーメッセージのテーブルにも対応する部分があります(下記参照)。

function mu.checkStatus( args )

statusパラメータの値を確認し、有効な値をargs.statusTableに代入します。エラーが発生した場合、エラーメッセージのテーブルにも対応する部分があります(下記参照)。

function mu.checkTypeAndGroup( args )

argsのタイプとグループの値を確認し、正しい値を代入します。エラーが発生した場合、エラーメッセージのテーブルにも対応する部分があります(下記参照)。

function mu.checkUrl( args )

argsのURLの値を確認し、正しい値を代入します。エラーが発生した場合、エラーメッセージのテーブルにも対応する部分があります(下記参照)。

タイプとグループの関数

function mu.getTypeParams( aType )

aTypeで指定されたタイプのテーブルを、モジュール:Marker utilities/Typesから取得して返します。該当するものがない場合、nilが返ります。

function mu.getTypeLabel( id )

idに対応するタイプの最初のラベルを返します。idは、vCardのタイプかウィキデータの項目名です。エラーがある場合、空文字列かidそのものが返ります。

function mu.typeExists( aType )

aTypeモジュール:Marker utilities/TypesにあればaTypeを、なければnilを返します。別名は対応するタイプに変換されます。

function mu.groupWithEvents( group )

指定されたグループgroupが、イベントのタイプを使用できるかどうかを返します。

function mu.getColor( args )

グループgroupから引数argscolorinverseを追加します。

function mu.idToType( id )

ウィキデータIDからタイプを検索し、見つかった場合はそのタイプを、そうでなければnilを返します。

整形関数

function mu.getShow( default, args, validValues )

上書き可能な既定値を踏まえて、カンマ区切りでパラメータに渡されたshowの値を持つ連想配列を返します。argsはパラメータのテーブルで、この関数内ではargs.showのみが評価されます。

function mu.removeCtrls( s, onlyInline )

この関数は、文字列sから制御文字や改行を削除します。onlyInline = falseの場合には改行は削除されません。この関数の返り値は2つあります:

  • 整形されたs
  • <div>タグのコンテナを持つ説明文descrDiv

クエリ関数

function mu.getCoordinatesFromWikidata( entity )

ウィキデータのデータセットにある座標をエンティティのインスタンスと共にに返します。まず P5140 プロパティから中心座標を取得しようと試み、次に P625 プロパティから座標の取得を試みます。

function mu.typeSearch( p31 )

いくつかの P31~P279 チェーンで、モジュール:Marker utilities/Typesのテーブルに値が含まれているかもしれないQIDを検索します。テーブル P31 には見つかったP31の値が含まれています。はじめにヒットした値が文字列で返されます。エラーの場合は、エラー文字列が返されます。上位レベルを検索する際の深さの最大数はmi.searchLimitで設定でき、通常は4です。最初の P279 ID だけが評価され、ツリー構造全体は評価されません。

function mu.getCommonsCategory( args, entity )

この関数は、ウィキデータにあるエンティティのインスタンスからコモンズのカテゴリを取得しようと試みます。最初に接続されているコモンズへのリンクが調べられ、次に P373 プロパティ、最後に P910 プロパティを調べます。

function mu.getLangTable( wikiLang, localLang )

wikiLanglocalLangcountry.lang)、モジュール:Marker utilities/i18nの言語langsを使って言語コードの配列を作成します。

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

この関数は、ウィキデータの情報を用いて、ウィキの言語と現地語の名称でテーブルの引数を埋めます。fromWikidataテーブルには、名称の情報をウィキデータから取得したかどうか(fromWikidata.namefromWikidata.nameLocal)が格納されます。

function mu.getArticleLink( args, entity )

この記事でテンプレート呼び出しが行われない限り、この関数は関連する記事へのサイトリンクをargs.thisWikiに渡します。

マーカー関数

function mu.getMaki( key )

Die Funktion liefert eine Tabelle für den angegebenen key aus der Tabelle Module:Marker utilities/Maki icons zurück.

function mu.getMakiIconId( aType )

Die Funktion liefert den Namen eines MAKI-Symbols zum Type aType oder nil zurück, wenn es keinen gibt oder eine Abbildung für den Fließtext fehlt.

function mu.addIconToMarker( args )

fügt einen Text, der ein Symbol enthält, zur Parametertabelle args.

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

liefert r: HTML-Quellcode des Marker-Symbols.

アイコン関数

function mu.makeStatusIcons( args )

args.statusTableテーブルに従って画像の文字列を返します。

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

リンクされたアイコン画像を表示するためのHTMLを生成します。表示にはテンプレートのスタイルシートも必要です。listing-iconに追加するCSSクラスclassesと、リンクlink(外部リンクでも内部リンクでも)、ツールチップtitle、通常は表示されないリンクテキストtextが必要です。

function mu.makeSisterIcons( icons, args, country, entity )

リンクされた姉妹プロジェクトのアイコンをテーブルに代入します。通常、情報はウィキデータ・エンティティのサイトリンクから取得します。argsは、テンプレートに渡されたパラメータのテーブルです。テーブルcountryには言語をはじめとした国のデータが入っています。

function mu.makeSocial( icons, args, fromWikidata, name )

リンクされたソーシャルメディアサービスのアイコンをテーブルに代入します。argsは、テンプレートに渡されたパラメータのテーブルです。fromWikidataはウィキデータから取得したパラメータのテーブル、nameはマーカーやvCardが紹介している場所の名称です。

出力関数

function mu.prepareNames( args )

nameパラメータとnameMapから形成されるdisplayNameテーブルとgivenNameテーブルを引数テーブルに追加します。ウィキ構文内の有効なリンクから実際の名前を抽出することが主な目的です。どちらのテーブルも次の3つの要素で構成されます:

  • namestring):表示される名前
  • allstring):テンプレートにリンク付きで渡された場合はリンクを含めたウィキ構文、そうでない場合は名前だけ
  • pageTitlestring):リンクの名前。nameと異なるかもしれません。
function mu.makeName( result, args, show, page, country, nameClass, localClass )

resultテーブルに名前と追加の名前を追加します。追加の名前には、別名、現地語での名前、コメント、空港コードが含まれ、括弧とspanタグに挟まれています。nameClasslocalClassは、名前と現地語名の追加クラスです。

function mu.parentheses( s, trim )

この関数は、括弧内に文字列sを挿入します。ブール値trimtrueならば、書式の文字列(通常は (%s))が切り抜かれます。

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

文字列rを返します。この文字列は、マップツールへのリンクを含む10進数の座標で、任意で括弧内に入れることもできます。nameは場所の名前で、fromWD = trueは座標がウィキデータ由来であることを意味します。また、extraParamsにはマップツールで使用される縮尺と領域のデータが入ります。

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

マーカーやvCardの内容をタグ(<span><div>)で囲みます。

その他の関数

function mu.getPageData()

この関数は、ページに関連したデータを返します。

関連項目

--[[
	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-10-15',
		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.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.getCategories( formatStr )
	return wu.getCategories( formatStr )
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
	end
	setCopyMarker( args, show )
	if args.wikidata ~= '' then
		if show.socialmedia then
			setSocialMedia( args, 'y' )
		elseif show.nosocialmedia then
			setSocialMedia( args, 'n' )
		end
	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
function mu.getMaki( key )
	mm = mm or require( 'Module:Marker utilities/Maki icons' )
	key = key:lower():gsub( '[_%s]+', '-' )
	return mm.icons[ key ]
end

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

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

-- making marker symbol
function mu.makeMarkerSymbol( args, frame )
	local extraClasses = args.inverse and ' listing-map-inverse' or
		' listing-map-not-inverse'
	if args.isIcon then
		extraClasses = extraClasses .. ' listing-map-is-symbol'
	end
	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
	
	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, link
	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
function mu.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
function mu.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

-- 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, t
	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

	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( result, s )
	end

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

	mu.tableInsert( result,	makeNameAffix( args, page, country, localClass,
		show ) )
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
		arg = args[ key ]:gsub( '<[^<>]*>', '' ) -- remove html tags
		wrapper:attr( value, data( arg ) )
	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