Módulo:VCard

De Wikiviajes, la guía libre de viajes
Ir a la navegación Ir a la búsqueda
Icono de documentación de módulo Documentación del módulo[ver] [editar] [historial] [purgar]

Version check

Designación de la versión en Wikidata: 2021-10-24 Contra Este módulo está desactualizado/obsoleto

Uso

Module used by the template {{VCard}}

Experimental Examples

* {{vCard | name= Ein Hotel | type = hostel | url= http://hotel.de | lat = 52.5144 | long = 13.389722 | show = all | address = Hauptstraße 1 | directions = Abzweig von der Nebenstraße | description = Das in der Innenstadt gelegene Hotel besitzt großzügige Räume, bietet aber nur ÜF. | phone = +49 (0)30 2345 1234 | fax = +49 (0)30 2345 9876, +49 0176 345 1234 | before = [[File:Flag of Germany.svg|border|20px|class=noviewer|Flag of Germany]] | image = Berlin Friedrichstraße Galeries Lafayette.jpg | email = info@hotel.de | comment = j | lastedit = 2020-09-18 | hours = 7/24. | checkin = ab 14 Uhr | checkout = bis 12 Uhr | payment = Visa, Master, AmEx, Maestro | subtype = wlan, bar, pool, room:89 | skype = nutzer.name; nutzer2.name }}

* {{vCard | wikidata = Q201219 | auto = j | name = Ägyptisches Museum Kairo | comment = auch Nationalmuseum | alt = Egyptian Museum | name-latin = al-Matḥaf al-Miṣrī | address = Mīdān et-Taḥrīr | address-local = ميدان التحرير | directions = im Stadtzentrum | directions-local = بوسط البلد }}
* {{vCard | wikidata = Q201219 | name = Ägyptisches Museum Kairo | facebook = j | twitter = j }} <​!-- Marker-Modus -->
* {{vCard | wikidata = Q1142142 | auto = j | name = Nordiska museet }} Noch weiterer Text.
* {{vCard | wikidata = Q1142142 | auto = j | price = n }}
* {{vCard | wikidata = Q257342 | auto = j }}
* {{vCard | wikidata = Q28934 | auto = j | description = Test Durchhangeln. }}
* {{vCard | wikidata = Q4872 | auto = j }}
* {{vCard | wikidata = Q10697 | auto = j }}
* {{vCard | wikidata = Q12508 | auto = j | description = Test Durchhangeln. }}
* {{vCard | wikidata = Q46033 | description = Test Flughafen. }}
* {{vCard | wikidata = Q46033 | show = symbol | description = Test Flughafen mit Ikone. }}
* {{vCard | wikidata = Q13218762 | auto = j | description = Anzeige englische Wikipedia. }}
* {{vCard | wikidata = Q47429618 | auto = j | description = Ausgabe Anschriften. | payment = Visa, Master }}
* {{vCard | wikidata = Q47429618 | address = | address-local = | directions = | phone = | show = nowdsubtype | description = Test eingeschränkte WD-Ausgabe. }}
* {{vCard | wikidata = Q637739 | auto = j | description = Rollstuhl aus WD. }}
* {{vCard | name = Landesmuseum für Vorgeschichte | type = museum | wikidata = Q1332407 | auto = y | description = Uhrzeiten, Kommentare. }}
  • Flag of Germany 1 Ein Hotel (j), Hauptstraße 1 (Abzweig von der Nebenstraße). +49 (0)30 2345 1234, fax: +49 (0)30 2345 9876, +49 (0)176 345 1234, : , Skype: nutzer.name, nutzer2.name. Das in der Innenstadt gelegene Hotel besitzt großzügige Räume, bietet aber nur ÜF. Características: Wifi, Bar, 89 Habitación, Piscina. 🕓 7/24. Hora de entrada: ab 14 Uhr. Hora de salida: bis 12 Uhr. Formas de pago aceptadas: Visa, Master, AmEx, Maestro. (52° 30′ 52″ N 13° 23′ 23″ O)
  • 1 Ägyptisches Museum Kairo (al-Matḥaf al-Miṣrī, Egyptian Museum, auch Nationalmuseum), Mīdān et-Taḥrīr, ميدان التحرير (im Stadtzentrum, بوسط البلد). Ägyptisches Museum Kairo en la enciclopedia Wikipedia Ägyptisches Museum Kairo en el directorio de medios Wikimedia CommonsÄgyptisches Museum Kairo (Q201219) en la base de datos Wikidata. (30° 2′ 52″ N 31° 14′ 0″ O)
  • 2 Ägyptisches Museum Kairo Ägyptisches Museum Kairo en la enciclopedia Wikipedia Ägyptisches Museum Kairo en el directorio de medios Wikimedia CommonsÄgyptisches Museum Kairo (Q201219) en la base de datos WikidataÄgyptisches Museum Kairo en FacebookÄgyptisches Museum Kairo en Twitter (30° 2′ 52″ N 31° 14′ 0″ O)
  • 3 Nordiska museet Nordiska museet en la enciclopedia Wikipedia Nordiska museet en el directorio de medios Wikimedia CommonsNordiska museet (Q1142142) en la base de datos Wikidata Noch weiterer Text.
  • 4 Museo nórdico Museo nórdico en la enciclopedia Wikipedia Museo nórdico en el directorio de medios Wikimedia CommonsMuseo nórdico (Q1142142) en la base de datos Wikidata
  • 1 Carnegie Library of Reims Carnegie Library of Reims en la enciclopedia Wikipedia Carnegie Library of Reims en el directorio de medios Wikimedia CommonsCarnegie Library of Reims (Q257342) en la base de datos Wikidata
  • 1 Aeropuerto de Bruselas-National (IATA: BRU). Aeropuerto de Bruselas-National en la enciclopedia Wikipedia Aeropuerto de Bruselas-National en el directorio de medios Wikimedia CommonsAeropuerto de Bruselas-National (Q28934) en la base de datos Wikidata. Test Durchhangeln.
  • 5 Museo Pushkin Museo Pushkin en la enciclopedia Wikipedia Museo Pushkin en el directorio de medios Wikimedia CommonsMuseo Pushkin (Q4872) en la base de datos Wikidata
  • 2 Biblioteca Pública de Colonia Biblioteca Pública de Colonia en la enciclopedia Wikipedia Biblioteca Pública de Colonia en el directorio de medios Wikimedia CommonsBiblioteca Pública de Colonia (Q10697) en la base de datos Wikidata
  • 6 pirámides de Guiza. pirámides de Guiza en la enciclopedia Wikipedia pirámides de Guiza en el directorio de medios Wikimedia Commonspirámides de Guiza (Q12508) en la base de datos Wikidata. Test Durchhangeln. (29° 58′ 34″ N 31° 7′ 58″ O)
  • 2 aeropuerto de Frankfurt (IATA: FRA). aeropuerto de Frankfurt en la enciclopedia Wikipedia aeropuerto de Frankfurt en el directorio de medios Wikimedia Commonsaeropuerto de Frankfurt (Q46033) en la base de datos Wikidata. Test Flughafen.
  • 30x-Airplane.png aeropuerto de Frankfurt (IATA: FRA). aeropuerto de Frankfurt en la enciclopedia Wikipedia aeropuerto de Frankfurt en el directorio de medios Wikimedia Commonsaeropuerto de Frankfurt (Q46033) en la base de datos Wikidata. Test Flughafen mit Ikone.
  • 7 Karnak Open Air Museum. Karnak Open Air Museum en la enciclopedia Wikipedia Karnak Open Air Museum en el directorio de medios Wikimedia CommonsKarnak Open Air Museum (Q13218762) en la base de datos Wikidata. Anzeige englische Wikipedia. (25° 43′ 10″ N 32° 39′ 27″ O)
  • 2 Steigenberger Hotel El Tahrir Cairo. Steigenberger Hotel El Tahrir Cairo en el directorio de medios Wikimedia CommonsSteigenberger Hotel El Tahrir Cairo (Q47429618) en la base de datos Wikidata. Ausgabe Anschriften. Características: ★★★★, Aparcamiento, Garaje, Wifi gratis, 2 Bares, Centro de bienestar, Gimnasio, 295 Habitación, Piscina, Restaurant. Formas de pago aceptadas: Visa, Master. (30° 2′ 50″ N 31° 14′ 9″ O)
  • 3 Steigenberger Hotel El Tahrir Cairo, 2 Kasr El Nil St., El Tahrir Sq., Cairo, ٢ شارع قصر النيل ، ميدان التحرير ، القاهرة. +20 (0)2 2575 0777, +20 (0)2 3854 2020, +20 16416. Steigenberger Hotel El Tahrir Cairo en el directorio de medios Wikimedia CommonsSteigenberger Hotel El Tahrir Cairo (Q47429618) en la base de datos Wikidata. Test eingeschränkte WD-Ausgabe. (30° 2′ 50″ N 31° 14′ 9″ O)
  • 3 Berliner Straße. Berliner Straße en la enciclopedia Wikipedia Berliner Straße en el directorio de medios Wikimedia CommonsBerliner Straße (Q637739) en la base de datos Wikidata. Rollstuhl aus WD. Característica: Acceso en silla de ruedas.
  • 8 Landesmuseum für Vorgeschichte, Richard-Wagner-Straße 9, 06114 Halle (Saale). +49 (0)345 52 47 30, fax: +49 (0)345 524 73 51, : . Landesmuseum für Vorgeschichte en la enciclopedia Wikipedia Landesmuseum für Vorgeschichte en el directorio de medios Wikimedia CommonsLandesmuseum für Vorgeschichte (Q1332407) en la base de datos WikidataLandesmuseum für Vorgeschichte en FacebookLandesmuseum für Vorgeschichte en InstagramLandesmuseum für Vorgeschichte en Twitter. Uhrzeiten, Kommentare. 🕓 Mar–Vie 9:00 am–5:00 pm; Sab–Dom, Feriado legal 10:00 am–6:00 pm; Cerrado: 24. Dic., 31. Dic. Precio: 5,00 € (adultos), 3,00 € (reducido), 2,50 € (niños, 6–14 años), 10,00 € (familia), 2,00 € (guía de audio).

Parameter show

  • Mögliche Werte: none, poi, coord, all, nosubtype, nowdsubtype, inline, noairport, outdent, symbol. Eine kommaseparierte Liste mehrerer Werte ist möglich.
  • Man kann die Standardwerte überschreiben.
  • Darstellung von Marker und Koordinaten.
    • none ist stärker als poi, coord und all. all ist stärker als poi und coord.
  • Die Ausgabe der Subtypen ist vorbereitet, wird aber zurzeit nicht standardmäßig durchgeführt (nosubtype).
    • nosubtype ist stärker als subtype.
  • Block-/Inline-Darstellung
    • Zukünftig wird die vCard standardmäßig im Block-Modus (css: display = block) angezeigt werden. Gegenwärtig Inline-Modus aufgrund zahlreicher Verwendung in diesem Modus. Dies schließt Beschränkungen bei den Darstellungsmöglichkeiten der Beschreibung description ein. inline schaltet vom Block- in den Inline-Modus.
    • outdent bewirkt im Blockmodus einen eingerückten Absatz mit hängender erster Zeile. Das POI-Symbol wirkt dann wie ein Aufzählungszeichen.
Esta documentación está transcluida desde Módulo:VCard/doc.
Los editores pueden experimentar en la zona de pruebas (crear) y en los casos de prueba (crear) del módulo.
Por favor, añade las categorías en la subpágina de documentación. Subpáginas de este módulo.

-- documentation
local vCard = {
	suite  = 'vCard',
	serial = '2021-09-03',
	item   = 58187507
}

-- module import
require( 'Module:No globals' )
local mi = require( 'Module:Marker utilities/i18n' )
local mu = require( 'Module:Marker utilities' )
local vp = require( 'Module:VCard/Params' ) -- parameter lists
local vi = require( 'Module:VCard/i18n' ) -- parameter translations
local vq = mw.loadData( 'Module:VCard/Qualifiers' ) -- comment tables

local cm = require( 'Module:CountryData' )
local hi -- modules will be loaded later if needed
local hr
local lg
local lp = require( 'Module:LinkPhone' )
local pd = require( 'Module:PageData' )
local wu = require( 'Module:Wikidata utilities' )
local yn = require( 'Module:Yesno' )

-- module variable and administration
local vc = {
	moduleInterface = vCard
}

local function addWdClass( key )
	return vp.wdContent[ key ] and ' wikidata-content' or ''
end

local function forceFetchFromWikidata( tab )
	for key, value in pairs( tab ) do
		vp.ParMap[ key ] = true
	end
end

-- copying args parameters to vp.ParMap parameters
local function copyParameters( args, show )
	local t, value

	-- force getting data from Wikidata for missing parameters
	show.inlineDescription = true -- description with div or span tag
	if vp.ParMap.auto == true then
		forceFetchFromWikidata( vp.ParWD )
		forceFetchFromWikidata( vp.ParWDAdd )
	end

	-- copying args parameters to vp.ParMap parameters
	for key, validKeys in pairs( vi.p ) do
		value = mu.getArgument( args, validKeys )

		if value then
			value, t =
				mu.removeCtrls( value, show.inline or key ~= 'description' )
			if t then
				show.inlineDescription = false
			end

			if key ~= 'auto' and key ~= 'show' and key ~= 'wikidata' then
				if value == '' then
					value = true
				end
				if value == '0' or value == '1' then
					t = nil
				else
					t = yn( value, nil ) -- takes 0 and 1 as false and true
				end

				if t ~= nil then
					if vp.ParMap.wikidata ~= '' then
						vp.ParMap[ key ] = t
					else
						vp.ParMap[ key ] = ''
					end
				else
					vp.ParMap[ key ] = value
				end
			end
		end
	end

	-- force fetching data from Wikidata if empty
	for key, value in ipairs( { 'name', 'type' } ) do
		if type( vp.ParMap[ value ] ) == 'boolean' or vp.ParMap[ value ] == '' then
			vp.ParMap[ value ] = true
		end
	end
end

local function initialParameterCheck( args )
	local country, entity, param, show, t, v, wrongQualifier

	mu.checkArguments( args, vi.p )

	vp.ParMap.wikidata, entity, wrongQualifier =
		wu.getEntity( mu.getArgument( args, vi.p.wikidata ) or '' )
	if wrongQualifier then
		mu.addMaintenance( mi.maintenance.wrongQualifier )
	elseif mu.isSet( vp.ParMap.wikidata ) then
		mu.addMaintenance( mi.maintenance.wikidata )
	end
	v = mu.getArgument( args, vi.p.auto )
	if mu.isSet( vp.ParMap.wikidata ) and v then
		if v == '' then
			v = 'y'
		end
		vp.ParMap.auto = yn( v, mi.options.defaultAuto )
	else
		vp.ParMap.auto = false
	end

	-- making phone number table
	t = {}
	for _, key in ipairs( vi.phones ) do
		if vi.p[ key ] then
			mu.tableInsert( t, mu.getArgument( args, vi.p[ key ] ) )
		end
	end
	-- getting country-specific technical parameters
	country = cm.getCountryData( entity, t )
	if country.fromWD then
		mu.addMaintenance( mi.maintenance.countryFromWD )
	end
	if country.cc ~= '' then
		country.trunkPrefix = lp.getTrunkPrefix( country.cc )
	end
	country.extra = mi.defaultSiteType
	if mu.isSet( country.iso_3166 ) then
		country.extra = country.extra .. '_region:' .. country.iso_3166
		-- country-specific default show
	end
	if mu.isSet( country.show ) then
		vp.ParMap.show = country.show
	end

	-- handling args and vp.ParMap show arrays
	v = mu.getArgument( args, vi.p.show )
	show = mu.getShow( vp.ParMap.show, v, vp.show )
	if v and v:find( 'inline', 1, true ) then
		show.inlineSelected = true
		mu.addMaintenance( mi.maintenance.inlineSelected )
	end
	show.name = true

	-- copying args parameters to vp.ParMap parameters
	copyParameters( args, show )
	-- checking coordinates and converting DMS to decimal coordinates if necessary
	mu.checkCoordinates( vp.ParMap )
	-- remove namespace from category
	mu.checkCommonsCategory( vp.ParMap )
	for _, param in ipairs( mi.maintenance.parameters ) do
		if mu.isSet( vp.ParMap[ param ] ) then
			mu.addMaintenance( mw.ustring.format( mi.maintenance.parameterUsed, param ) )
		end
	end

	vp.ParMap.subtypeAdd = not show.nosubtype and not show.nowdsubtype

	if type( vp.ParMap.lastedit ) == 'string' and vp.ParMap.lastedit ~= ''
		and not vp.ParMap.lastedit:match( mi.dates.yyyymmdd.p ) then
		mu.addMaintenance( mi.maintenance.wrongDate )
		vp.ParMap.lastedit = ''
	end

	return entity, show, country
end

local function getQuantity( value, formatter, page )
	local a, f, u, unit, unitId
	if type( value ) == 'number' then
		return tostring( value )
	elseif value.amount == '0' then
		return '0'
	else
		a = mu.formatNumber( value.amount )
		u = ''
		unitId = value.unit
		unit = cm.getCurrency( unitId )
		if mu.isSet( unit ) then
			f = cm.getCurrency( 'default' ) or '%s unit'
			unit = unit.f and unit.f or f:gsub( 'unit', unit.iso )
		else
			unit = vq.labels[ unitId ]
		end
		if unit and unit:find( '%s', 1, true ) then
			a = mw.ustring.format( unit, a )
		elseif unit then
			u = unit
		elseif mw.wikibase.isValidEntityId( unitId ) then
			-- currency code
			u = wu.getValue( unitId, mi.properties.iso4217 )
			if u == '' then
				-- unit symbol
				u = wu.getValuesByLang( unitId, mi.properties.unitSymbol, 1,
					page.lang )
				u = u[ 1 ] or ''
			end
			if u ~= '' then
				mu.addMaintenance( mi.maintenance.unitFromWD )
			else
				u = unitId
				mu.addMaintenance( mi.maintenance.unknownUnit )
			end
		end
		if a ~= '' and u ~= '' and formatter ~= '' and
			formatter:find( '$1', 1, true ) and formatter:find( '$2', 1, true ) then
			a = mw.ustring.gsub( f, '($1)', a )
			a = mw.ustring.gsub( a, '($2)', u )
		else
			a = ( u ~= '' ) and a .. '&nbsp;' .. u or a
		end
	end
	return a
end

local function getHourModules()
	if not hr then
		hi = require( 'Module:Hours/i18n' )
		hr = require( 'Module:Hours' )
	end
end

local function getLabel( id )
	local label = id
	local tables = { vq.labels }
	if hi then
		table.insert( tables, hi.dateIds )
	end
	if id ~= '' and id:find( '^Q%d' ) then
		for _, tab in ipairs( tables ) do
			if type( tab[ id ] ) == 'string' then
				label = tab[ id ]
				break
			end
		end
		if label == '' then
			label = wu.getLabel( id ) or ''
			if label == '' then
				mu.addMaintenance( mi.maintenance.unknownLabel )
			else
				mu.addMaintenance( mi.maintenance.labelFromWD )
			end
		end
	end
	return label
end

-- getting comments for contacts and prizes from Wikidata using tables
local function getComments( statement, properties, page )
	local comments = {}
	local isMobilephone = false
	local minAge, maxAge
	for _, property in ipairs( properties ) do
		local pType = property .. '-type'
		if statement[ property ] then
			if property == mi.properties.minimumAge then
				minAge = getQuantity( statement[ property ][ 1 ], '', page )
			elseif property == mi.properties.maximumAge then
				maxAge = getQuantity( statement[ property ][ 1 ], '', page )
			end

			for _, id in ipairs( statement[ property ] ) do
				if statement[ pType ] == 'monolingualtext' then
					id = id.text
				elseif statement[ pType ] == 'time' then
					id, _ = wu.getDateFromTime( id )
					id = mw.ustring.format( mi.texts.asOf, id )
				elseif type( id ) == 'table' then
					id = ''
				end
				if id == mi.qualifiers.mobilePhone then
					isMobilephone = true
				else
					mu.tableInsert( comments, getLabel( id ) )
				end
			end
		end
	end

	if minAge and maxAge then
		mu.tableInsert( comments, mw.ustring.format( mi.texts.fromTo,
			minAge:gsub( '(%d+).*', '%1' ), maxAge ) )
	elseif minAge then
		mu.tableInsert( comments, mw.ustring.format( mi.texts.from, minAge ) )
	elseif maxAge then
		mu.tableInsert( comments, mw.ustring.format( mi.texts.to, maxAge ) )
	end

	if #comments > 0 then
		mu.addMaintenance( mi.maintenance.commentFromWD )
		return ' (' .. table.concat( comments, ', ' ) .. ')', isMobilephone
	else
		return '', isMobilephone
	end
end

local function hasValue( tab, val )
	for _, value in ipairs( tab ) do
		if value == val then
			return true
		end
	end
	return false
end

local function getLngProperty( lng, p )
	if not mu.isSet( lng ) then
		return ''
	end

	if not lg then
		lg = require( 'Module:Languages' )
	end
	local item = lg.lngProps[ lng ]
	if not item then
		local hyphen = lng:find( '-', 1, true )
		if hyphen and hyphen > 1 then
			item = lg.lngProps[ lng:sub( 1, hyphen - 1 ) ]
		end
	end
	if item then
		item = item[ p ]
	end

	return item or ( p == 'c' and 0 or '' )
end

--[[
properties are defined in Module:vCard/Params

p property or set of properties
f formatter string
c maximum count of results, default = 1
m concat mode (if c > 1), default concat with ', '
v value type,
	empty: string value (i.e. default type),
	id:    string value of an id like Q1234567  
	idl:   string value of the label of an id like Q1234567
	il:    language-dependent string value
	iq:    string value with qualifier ids
	au:    quantity consisting of amount and unit
	pau:   quantity consisting of amount (for P8733)
	vq:    string or table value with qualifiers ids and references
l = true: language dependent
l = wiki / local: monolingual text by wiki or local language
le = true: use date for lastedit parameter
--]]

-- function returns an array in any case
local function getWikidataValues( propDef, entity, page, country )
	local r = ''
	local ar = {}
	local a, i, mobilePhone, id, q, t, u, w

	-- setting defaults
	propDef.v = propDef.v or ''
	propDef.f = propDef.f or ''
	propDef.c = propDef.c or 1

	-- getting value arrays
	if propDef.l == 'wiki' then
		ar = wu.getValuesByLang( entity, propDef.p, propDef.c, page.lang )
	elseif propDef.l == 'local' then
		ar = wu.getValuesByLang( entity, propDef.p, propDef.c, country.lang )
	elseif propDef.l == true and propDef.c == 1 then
		id = getLngProperty( country.lang, 'q' )
		if id == '' then
			country.unknownLanguage = true
		else
			-- language of work or name
			a = wu.getValuesByQualifier( entity, propDef.p, mi.properties.languageOfName, id )
			if next( a ) then
				i = a[ getLngProperty( page.lang, 'q' ) ] -- item in page.lang
					or a[ id ] -- item in country lang
					or a[ next( a, nil ) ] -- first item
				ar = { i }
			end
		end
	elseif propDef.v == 'iq' then
		ar = wu.getValuesWithQualifiers( entity, propDef.p, propDef.q,
			mi.propTable.quantity, { mi.properties.retrieved }, propDef.c )
		if propDef.le then
			vp.ParMap.lastedit = wu.getLastedit( vp.ParMap.lastedit, ar )
		end
	elseif propDef.v == 'au' or propDef.v == 'vq' then
		q = propDef.v == 'au' and mi.propTable.feeComments or
			mi.propTable.contactComments
		ar = wu.getValuesWithQualifiers( entity, propDef.p, nil, q,
			{ mi.properties.retrieved }, propDef.c )
		-- maybe a change of nil to a properties table is useful
		if propDef.le then
			vp.ParMap.lastedit = wu.getLastedit( vp.ParMap.lastedit, ar )
		end
	else
		ar = wu.getValues( entity, propDef.p, propDef.c )
	end
	if #ar == 0 and propDef.p ~= mi.properties.instanceOf then
		return ar
	elseif propDef.p == mi.properties.instanceOf then -- instance of
		return { mu.typeSearch( ar, entity ) }
	end

	for i = #ar, 1, -1 do
		-- amount with unit (for fees)
		if propDef.v == 'au' then
			a = getQuantity( ar[ i ].value, propDef.f, page )
			if a == '0' then
				a = vq.labels.gratis
			end
			u, _ = getComments( ar[ i ], mi.propTable.feeComments, page )
			ar[ i ] = a .. u

		-- for number of rooms P8733
		elseif propDef.v == 'pau' then
			if ar[ i ].unit == '1' then
				a = tonumber( ar[ i ].amount ) or 0
			else
				a = 0
			end
			ar[ i ] = {}
			ar[ i ][ mi.properties.quantity ] = { a }
			ar[ i ][ mi.properties.quantity .. '-type' ] = 'quantity'
			ar[ i ].value = mi.qualifiers.roomNumber
			ar[ i ]['value-type'] = 'wikibase-entityid'

		-- qualifier ids (for subtypes)
		elseif propDef.v == 'iq' then
			if ar[ i ][ 'value-type' ] ~= 'wikibase-entityid' then
				table.remove( ar, i )
			end

		-- strings with qualifiers (for contacts)
		elseif propDef.v == 'vq' then
			if ar[ i ][ 'value-type' ] ~= 'string' then
				table.remove( ar, i )
			else
				u, mobilePhone =
					getComments( ar[ i ], mi.propTable.contactComments, page )
				if mi.options.useMobile and propDef.t then
					if ( mobilePhone and propDef.t == 'mobile' ) or
						( not mobilePhone and propDef.t == 'landline' ) then
						ar[ i ] = ar[ i ].value .. u
					else
						table.remove( ar, i )
					end
				else
					ar[ i ] = ar[ i ].value .. u
				end
			end

		-- value, monolingual text, identifier
		else
			if propDef.v == 'id' then
				ar[ i ] = ar[ i ].id
			elseif propDef.v == 'idl' then
				getHourModules()
				ar[ i ] = hr.formatTime( getLabel( ar[ i ].id ) )
			end
			if ar[ i ] ~= '' and propDef.f ~= '' then
				ar[ i ] = mw.ustring.format( propDef.f, ar[ i ] )
			end
		end
		if ar[ i ] == '' then
			table.remove( ar, i )
		end
	end
	return ar
end

local function getWikidataItem( parWDitem, entity, page, country )
	local arr = {}
	local subArr

	local function singleProperty( propDef )
		if #arr == 0 then
			arr = getWikidataValues( propDef, entity, page, country )
		else
			subArr = getWikidataValues( propDef, entity, page, country )
			for i = 1, #subArr, 1 do -- move to arr
				table.insert( arr, subArr[ i ] )
			end
		end
	end

	local p = parWDitem
	if not p then
		return ''
	end

	p.c = p.c or 1 -- count
	local tp = type( p.p )
	if tp == 'string' then
		singleProperty( p )
	elseif tp == 'table' then
		for key, sngl in ipairs( p.p ) do
			if type( sngl ) == 'table' then
				singleProperty( sngl )
			end
		end
	end

	if #arr > p.c then
		for i = #arr, p.c + 1, -1 do -- delete supernumerary values
			table.remove( arr, i )
		end
	end
	if p.m == 'no' then
		return arr
	else
		return table.concat( arr, p.m or ', ' )
	end
end

local function getAddressesFromWikidata( page, country, entity )
	local addresses = {}
	local t, u, w, weight

	-- getting addresses from Wikidata but only if necessary
	if vp.ParMap.address == true or vp.ParMap.addressLocal == true then
		-- P6375: address
		addresses = wu.getMonolingualValues( entity, mi.properties.streetAddress )
		if next( addresses ) then -- sometimes addresses contain <br> tag(s)
			for key, value in pairs( addresses ) do
				addresses[ key ] = value:gsub( '</*br%s*/*>', ' ' )
			end
		else
			return
		end
	else
		return
	end

	if vp.ParMap.address == true then
		vp.ParMap.address = addresses[ page.lang ]
		-- select address if the same writing system is used
		if not vp.ParMap.address then
			weight = -1
			u = getLngProperty( page.lang, 'w' ) -- writing entity id
			for key, value in pairs( addresses ) do
				-- same writing entity id as page.lang
				w = getLngProperty( key, 'w' )
				if w == '' then
					country.unknownLanguage = true
				else
					if key and w == u then -- same writing entity id
						w = getLngProperty( key, 'c' ) -- getting language weight
						if w > weight then -- compare language weight
							vp.ParMap.address = value
							vp.ParMap.addressLang = key
							weight = w
						end
					end
				end
			end
		end
		if not vp.ParMap.address then
			for _, lng in ipairs( mi.langs.address ) do
				if addresses[ lng ] then
					vp.ParMap.address = addresses[ lng ]
					vp.ParMap.addressLang = lng
					break
				end
			end
		end
		if not vp.ParMap.address then
			vp.ParMap.address = ''
			vp.ParMap.addressLang = ''
		end
		vp.wdContent.address = vp.ParMap.address ~= ''
	end

	-- removing county name from the end of address
	-- same with county name in county language and English
	if type( vp.ParMap.address ) == 'string' then
		vp.ParMap.address = mw.ustring.gsub( vp.ParMap.address,
			'[.,;]*%s*' .. country.country .. '$', '' )
	end

	t = true
	for _, lng in ipairs( mi.langs.address ) do
		if country.lang == lng then
			t = false
		end
	end
	if t and vp.ParMap.addressLocal == true
		and country.lang ~= page.lang then
		if country.lang ~= '' then
			vp.ParMap.addressLocal = addresses[ country.lang ] or ''
		else
			-- unknown language, maybe missing in Module:Languages
			vp.ParMap.addressLocal = addresses.unknown or ''
		end
		vp.wdContent.addressLocal = vp.ParMap.addressLocal ~= ''
	end
end

local function getDataFromWikidata( page, country, entity )
	if vp.ParMap.wikidata == '' then
		return
	end

	-- except local data if wiki language == country language
	if page.lang == country.lang then
		for key, value in ipairs( { 'nameLocal', 'addressLocal', 'directionsLocal' } ) do
			if type( vp.ParMap[ value ] ) == 'boolean' then
				vp.ParMap[ value ] = ''
			end
		end
	end

	mu.getNamesFromWikidata( vp.ParMap, vp.wdContent, page, country, entity )
	-- if it is actively used also in Module:Marker, then copy following lines
	-- to Module:Marker utilities
	if vp.wdContent.nameLocal then
		mu.addMaintenance( mi.maintenance.localNameFromWD )
	end

	getAddressesFromWikidata( page, country, entity )
	if vp.ParMap.hours == true then
		local lastEdit
		getHourModules()
		vp.ParMap.hours, lastEdit = hr.getHoursFromWikidata( entity, page.lang,
			mi.langs.name, mi.maintenance.properties, nil, vp.ParMap.lastedit,
			vq.labels )
		vp.wdContent.hours = vp.ParMap.hours ~= ''
		if mi.options.lasteditHours then
			vp.ParMap.lastedit = lastEdit
		end
	end

	for key, value in pairs( vp.ParWD ) do
		if vp.ParMap[ key ] == true then
			vp.ParMap[ key ] =
				getWikidataItem( vp.ParWD[ key ], entity, page, country )
			vp.wdContent[ key ] = vp.ParMap[ key ] ~= ''
		end
	end

	mu.getArticleLink( vp.ParMap, entity, page )
	mu.getCommonsCategory( vp.ParMap, entity )
	mu.getCoordinatesFromWikidata( vp.ParMap, vp.wdContent, entity )
end

local function compareLocal( value1, key2 )
	if not mu.isSet( value1 ) or not mu.isSet( vp.ParMap[ key2 ] ) then
		return
	end
	local s1 = mw.ustring.lower( value1 )
	local s2 = mw.ustring.lower( vp.ParMap[ key2 ] )
	local minLen = math.min( mw.ustring.len( s1 ), mw.ustring.len( s2 ) )
	if s1 == s2 or ( minLen > 0 and mw.ustring.sub( s1, 1, minLen ) ==
		mw.ustring.sub( s2, 1, minLen ) ) then
		vp.ParMap[ key2 ] = ''
	end
end

local function finalParameterCheck( show, page, country, defaultType, entity )
	-- remove boolean values from parameters to have only strings
	for key, value in pairs( vp.ParMap ) do
		if type( vp.ParMap[ key ] ) == 'boolean' then
			vp.ParMap[ key ] = ''
		end
	end
	-- image check
	if not vp.wdContent.image or mi.options.WDmediaCheck then 
		vp.ParMap.image = mu.checkImage( vp.ParMap.image, entity )
	end

	-- use local name if name is not given
	if vp.ParMap.name == '' and vp.ParMap.nameLocal ~= '' then
		vp.ParMap.name = vp.ParMap.nameLocal
		vp.ParMap.nameLocal = ''
	end
	-- missing name
	if vp.ParMap.name == '' then
		vp.ParMap.name = mi.maintenance.missingName
		mu.addMaintenance( mi.maintenance.missingNameMsg )
	end
	-- handling linked names like [[article|text]]
	vp.ParMap.givenName = mu.getName( vp.ParMap.name, vp.ParMap.wikiPage )

	-- identical names
	compareLocal( vp.ParMap.givenName.name, 'nameLocal' )
	compareLocal( vp.ParMap.givenName.name, 'alt' )
	compareLocal( vp.ParMap.givenName.name, 'comment' )
	compareLocal( vp.ParMap.nameLocal, 'alt' )
	compareLocal( vp.ParMap.directions, 'directionsLocal' )
	compareLocal( vp.ParMap.address, 'addressLocal' )

	-- analysing addressLocal vs address
	if vp.ParMap.addressLang and vp.ParMap.addressLang == country.lang then
		vp.ParMap.addressLocal = ''
	end
	if vp.ParMap.addressLocal ~= '' then
		vp.ParMap.addressLocal =
			mu.languageSpan( vp.ParMap.addressLocal, mi.texts.hintAddress, page, country )
		if vp.ParMap.address == '' then
			vp.ParMap.address = vp.ParMap.addressLocal
			vp.ParMap.addressLocal = ''
			vp.wdContent.address = vp.wdContent.addressLocal
		end
	end

	-- names shall not contain tags or template calls
	if vp.ParMap.name:find( '<', 1, true ) or vp.ParMap.name:find( '{{', 1, true ) or 
		vp.ParMap.nameLocal:find( '<', 1, true ) or vp.ParMap.nameLocal:find( '{{', 1, true ) then
		mu.addMaintenance( mi.maintenance.malformedName )
	end

	show.noCoord = vp.ParMap.lat == '' or vp.ParMap.long == ''
	if show.noCoord then
		show.coord = nil
		show.poi   = nil
		mu.addMaintenance( mi.maintenance.missingCoordVc )
	end

	-- getting Marker type and group
	if not mu.isSet( vp.ParMap.type ) and mu.isSet( defaultType ) then
		vp.ParMap.type = defaultType
	end
	mu.checkTypeAndGroup( vp.ParMap )
	if mi.options.useTypeCateg and vp.ParMap.typeTable then
		for _, aType in ipairs( vp.ParMap.typeTable ) do
			mu.addMaintenance( mw.ustring.format( mi.maintenance.type, aType ) )
		end
	end

	vp.ParMap.zoom = math.floor( tonumber( vp.ParMap.zoom ) or mi.defaultZoomLevel )
	if vp.ParMap.zoom < 0 or vp.ParMap.zoom > mi.maxZoomLevel then
		vp.ParMap.zoom = mi.defaultZoomLevel
	end

	vp.ParMap.color = mu.getColor( vp.ParMap.group )
	vp.ParMap.commonscat = vp.ParMap.commonscat:gsub( ' ', '_' )

	if mu.isSet( vp.ParMap.description ) and mw.ustring.match( vp.ParMap.description, '[%w_€$]$' ) then
		vp.ParMap.description = vp.ParMap.description .. '.'
	end
end

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

local function formatText( tab, key, class )
	if not mu.isSet( vp.ParMap[ key ] ) then
		return
	end

	local r
	local textKey = key
	if key == 'hours' then
		vp.ParMap[ key ], r = mw.ustring.gsub( vp.ParMap[ key ],
			mi.texts.closedPattern, '' )
		textKey = ( r > 0 ) and 'closed' or key
	end
	r = mw.ustring.format( mi.texts[ textKey ], vp.ParMap[ key ] )
	r =	mw.ustring.gsub( r, '^%a', mw.ustring.upper )
	r = r .. ( r:sub( -1 ) == '.' and '' or '.' )

	table.insert( tab, makeSpan( r, class .. addWdClass( key ) ) )
end

local function formatPhone( key, country )
	if not mu.isSet( vp.ParMap[ key ] ) then
		return ''
	end
	
	local class
	local pArgs = {
		phone = vp.ParMap[ key ],
		cc = country.cc,
		isFax = false,
		isTollfree = false,
		format = false
	}
	if vp.wdContent[ key ] then
		pArgs.format = true
		pArgs.size = country.phoneDigits or 2
	end

	if key == 'fax' then
		class = 'p-tel-fax fax listing-fax' .. addWdClass( key )
		pArgs.isFax = true
	else
		class = 'p-tel tel listing-phone' .. addWdClass( key )
		if key == 'phone' then
			class = class .. ' listing-landline'
		elseif key == 'tollfree' then
			class = class .. ' listing-tollfree'
			pArgs.isTollfree = true
		elseif key == 'mobile' then
			class = class .. ' listing-mobile'
		end
	end
	return mw.ustring.format( mi.texts[ key ],
		makeSpan( lp.linkPhoneNumbers( pArgs ), class ) )
end

local function makeIcons( page, country, entity, withFullStop )
	local r = ''
	local name = vp.ParMap.givenName.name
	local t, s = mu.makeSisterIcons( vp.ParMap, page, country, entity )
	-- social media including value check
	local u = mu.makeSocial( vp.ParMap, vp.wdContent, name, true )
	t = t .. u

	if t == '' or not withFullStop then
		return t
	end
	if t == s then
		-- only Wikidata icon. This is not visible for readers who are not logged in.
		return t:gsub( '</span>', '.</span>' )
	end
	return t .. makeSpan( '.', u ~= '' and 'listing-social-media listing-full-stop'
		or 'listing-sister-icon listing-full-stop' )
end

local function formatDate( aDate, aFormat )
	return mw.getContentLanguage():formatDate( aFormat, aDate, true )
end

local function removeFullStops( s )
	-- closing (span) tags between full stops
	return s:gsub( '%.+(</[%l<>/]+>)%.+', '%1.' )
		:gsub( '%.%.+', '.' )
end

-- create unesco image with link and title
local function getUnescoImage( country, continent )
	local title = mi.imgTitles[ continent ]
	if title then
		title = title .. '|link=' .. mi.articles[ continent ] .. '#' .. country
	else
		title = mi.imgTitles.default .. '|link=' .. mi.articles.default
	end
	return mw.ustring.format( mi.icons.unesco, title )
end

local function makeMarkerAndName( show, page, country, frame )
	local r = ''
	local m, s, t

	if vp.ParMap.before ~= '' then
		r = r .. vp.ParMap.before .. ' '
	end

	-- adding POI marker
	if show.poi then
		vp.ParMap.symbol = '-number-' .. vp.ParMap.group
		if show.symbol then
			s = mu.getMakiIconName( vp.ParMap.type )
			if s then
				m = mu.getMaki( s )
				if m and m.im then
					vp.ParMap.symbol = s
					vp.ParMap.text = mw.ustring.format( '[[File:%s|x14px|link=|class=noviewer]]',
						m.im )
				end
			end
		end
		vp.ParMap.useIcon = false
		r = r ..
			mu.makeMarkerSymbol( vp.ParMap, vp.ParMap.givenName.all, frame ) .. '&nbsp;'
	end
	
	-- adding names, url, comment and airport codes
	vp.ParMap.url = mu.checkUrl( vp.ParMap.url )

	if vp.ParMap.styles ~= '' then
		s = mi.nameStyles[ vp.ParMap.styles:lower() ] or vp.ParMap.styles;
	else
		s = nil
	end

	t = vp.ParMap.nameExtra ~= '' and ( ' ' .. vp.ParMap.nameExtra ) or ''
	if vp.ParMap.url ~= '' and vp.ParMap.givenName.pageTitle == '' then
		t = '[' .. vp.ParMap.url .. ' '
			.. mu.replaceBrackets( vp.ParMap.givenName.name ) .. ']' .. t
	else
		t = vp.ParMap.givenName.all .. t
	end
	r = r .. tostring( mw.html.create( 'bdi' )
		:attr( 'id', 'vCard_' .. mw.uri.anchorEncode( vp.ParMap.givenName.name ) )
		:attr( 'class', 'p-name p-org fn org listing-name' .. addWdClass( 'name' ) )
		:cssText( s )
		:wikitext( t )
	)

	if vp.ParMap.url ~= '' and vp.ParMap.givenName.pageTitle ~= '' then
		r = r .. ' ' ..  makeSpan( '[' .. vp.ParMap.url .. ' ' .. mi.icons.internet .. ']',
			'listing-url' )
	end

	local tab = {}
	if mi.options.showLocalData then
		if vp.ParMap.nameLocal ~= '' then
			mu.tableInsert( tab, makeSpan(
				mu.languageSpan( vp.ParMap.nameLocal, mi.texts.hintName, page, country ),
				'listing-name-local' .. addWdClass( 'nameLocal' ) ) )
		end
		if vp.ParMap.nameLatin ~= '' then
			mu.tableInsert( tab, makeSpan( vp.ParMap.nameLatin,
				'listing-name-latin listing-emphasized', mi.texts.hintLatin ) )
		end
	end
	if vp.ParMap.alt ~= '' then
		mu.tableInsert( tab, makeSpan( vp.ParMap.alt, nil, nil, true ) )
	end
	t = table.concat( tab, ', ' )
	if t ~= '' then
		t = makeSpan( t, 'p-nickname nickname listing-alt' )
	end
	if t ~= '' then
		tab = { t }
	else
		tab = {}
	end
	if vp.ParMap.comment ~= '' then
		table.insert( tab, makeSpan( vp.ParMap.comment,
			'listing-comment listing-emphasized' , nil, true ) )
	end
	if not show.noairport and vp.ParMap.type == mi.airportType then
		mu.tableInsert( tab, mu.makeAirport( vp.ParMap, vp.wdContent ) )
	end
	t = table.concat( tab, ', ' )
	if t ~= '' then
		r = r .. ' (' .. t .. ')'
	end

	return r
end

local function makeEvent( page )
	local isEvent = false
	local s = {}
	local count = 0 -- counts from-to statements
	local startMonth -- month of start date
	local today = page.langObj:formatDate( 'Y-m-d', 'now', true )
	local todayYear = today:sub( 1, 4 )  -- yyyy
	local todayMonth = today:sub( 6, 7 ) -- mm
	local lastDate = ''
	local lastYear = ''
	local useYMD -- both dates are yyyy-mm-dd

	local function makePeriod( beginP, endP )
		if beginP == endP then
			endP = ''
		end
		if mu.isSet( beginP ) and mu.isSet( endP ) then
			count = count + 1
			return mw.ustring.format( mi.texts.fromTo2, beginP, endP )
		elseif mu.isSet( beginP ) then
			return beginP
		else
			return endP
		end
	end

	local function analyseDate( d, m, y )
		local success, c, t

		if useYMD then
			success, t = pcall( formatDate, d, mi.dates.yyyymmdd.f )
			success, c = pcall( formatDate, d, 'Y-m-d' )
			if success then
				lastDate = c > lastDate and c or lastDate
				d = t
			end
			return d, nil
		end

		if d:match( mi.dates.yyyymmdd.p ) then
			y = d:sub( 1, 4 )
			d = d:sub( 6 )
		end
		if mu.isSet( y ) then
			if y:match( mi.dates.yy.p ) then
				y = ( '2000' ):sub( -#y ) .. y
			elseif not y:match( mi.dates.yyyy.p ) then
				y = nil
			end
			lastYear = y > lastYear and y or lastYear
		end
		if mu.isSet( d ) and mu.isSet( m ) and d:match( mi.dates.dd.p ) and
			not m:match( mi.dates.mm.p ) then
			-- try to convert month to number string
			success, t = pcall( formatDate, m, 'm' )
			if success then
				m = t
			else
				for i = 1, 12, 1 do
					if m == mi.months[ i ] or mw.ustring.match( m, mi.monthAbbr[ i ] ) then
						m = '' .. i
						break
					end
				end
			end
		end
		if mu.isSet( d ) and mu.isSet( m ) and d:match( mi.dates.dd.p ) and
			m:match( mi.dates.mm.p ) then
			d = m:gsub( '%.+$', '' ) .. '-' .. d:gsub( '%.+$', '' )
			m = nil
		elseif mu.isSet( d ) and not mu.isSet( m ) and d:match( mi.dates.dd.p ) then
			d = ( startMonth or todayMonth ) .. '-' .. d:gsub( '%.+$', '' )
		end
		if mu.isSet( d ) then
			if d:match( mi.dates.mmdd.p ) then
				startMonth = d:gsub( '%-%d+', '' )
				m = nil
				c = ( y or todayYear ) .. '-' .. d
				success, t = pcall( formatDate, c, mi.dates.mmdd.f )
				if success then
					d = t
				end
			elseif d:match( mi.dates.dd.p ) and not mu.isSet( m ) and startMonth then
				c = ( y or todayYear ) .. '-' .. startMonth .. '-' .. d
				success, t = pcall( formatDate, c, mi.dates.mmdd.f )
				if success then
					d = t
				end
			end
		end
		if mu.isSet( m ) then
			d = ( mu.isSet( d ) and ( d .. ' ' ) or '' ) .. m
		end
		return d, y
	end

	if vp.ParMap.group ~= mi.eventGroup then
		return ''
	end
	for _, param in ipairs( { 'date', 'month', 'year', 'endDate', 'endMonth',
		'endYear', 'frequency', 'location' } ) do
		if mu.isSet( vp.ParMap[ param ] ) then
			isEvent = true
			break
		end
	end
	if not isEvent then
		return ''
	end

	if mu.isSet( vp.ParMap.frequency ) then
		table.insert( s, makeSpan( vp.ParMap.frequency, 'listing-frequency' ) )
	else
		if vp.ParMap.date:match( mi.dates.yyyymmdd.p ) and
			vp.ParMap.endDate:match( mi.dates.yyyymmdd.p ) then
			useYMD = true
			if vp.ParMap.date > vp.ParMap.endDate then
				vp.ParMap.date, vp.ParMap.endDate = vp.ParMap.endDate, vp.ParMap.date
			end
		end
		vp.ParMap.date, vp.ParMap.year
			= analyseDate( vp.ParMap.date, vp.ParMap.month, vp.ParMap.year )
		vp.ParMap.endDate, vp.ParMap.endYear
			= analyseDate( vp.ParMap.endDate, vp.ParMap.endMonth, vp.ParMap.endYear )

		local d = {}
		mu.tableInsert( d, makePeriod( vp.ParMap.date, vp.ParMap.endDate ) )
		mu.tableInsert( d, makePeriod( vp.ParMap.year, vp.ParMap.endYear ) )
		mu.tableInsert( s, makeSpan( table.concat( d, count > 1 and ', ' or ' ' ),
			'listing-date' ) )

		if ( lastYear ~= '' and lastYear < todayYear ) or 
			( lastDate ~= '' and lastDate < today ) then
			mu.addMaintenance( mi.maintenance.outdated )
		end
	end
	
	if mu.isSet( vp.ParMap.location ) then
		local locations = mu.textSplit( vp.ParMap.location, ',' )
		for _, location in ipairs( locations ) do
			if location ~= page.subpageText and location ~= page.text
				and mw.title.new( location, '' ).exists then
				location = makeSpan( '[[' .. location .. ']]', 'listing-location' )
			end
			table.insert( s, location )	
		end
	end

	s = table.concat( s, ', ' )
	return ( s ~= '' and ': ' or '' ) .. s
end

local function makeAddressAndDirections( page, country )
	local r = ''
	local t

	if vp.ParMap.address ~= '' then
		if mu.isSet( vp.ParMap.addressLang ) then
			t = mw.language.fetchLanguageName( vp.ParMap.addressLang, page.lang ) or ''
			if t == '' then
				country.unknownLanguage = true
				t = nil
			end
		end

		r = r .. ', ' .. tostring( mw.html.create( 'bdi' )
			:attr( 'class', 'p-adr adr listing-address' .. addWdClass( 'address' ) )
			:attr( 'lang', mu.data( vp.ParMap.addressLang ) )
			:attr( 'title', t and mw.ustring.format( mi.texts.hintAddress2, t ) or nil )
			:wikitext( makeSpan( vp.ParMap.address, 'p-street-address street-address' ) )
		)
	end
	if mi.options.showLocalData and vp.ParMap.addressLocal ~= '' then
		r = r .. makeSpan( ', ' .. 
			makeSpan( vp.ParMap.addressLocal, 'listing-address-local' .. addWdClass( 'addressLocal' ) ),
			'listing-add-info'
		)
	end

	t = ''
	if vp.ParMap.directions ~= '' then
		t = makeSpan( vp.ParMap.directions, 'listing-directions listing-emphasized'
			.. addWdClass( 'directions' ) )
	end
	local s = ''
	if mi.options.showLocalData and vp.ParMap.directionsLocal ~= '' then
		s = makeSpan(
			mu.languageSpan( vp.ParMap.directionsLocal, mi.texts.hintDirections, page, country ),
			'listing-directions-local' .. addWdClass( 'directionsLocal' ) )
		if t ~= '' then
			s = ', ' .. s
		end
		s = makeSpan( s, 'listing-add-info' )
	end
	if ( t .. s ) ~= '' then
		r = r .. ' (' .. t .. s .. ')'
	end
	if r ~= '' then
		r = r .. '. '
		if not r:find( '^,' ) and not r:find( '^ %(' ) then
			r = '. ' .. r
		end
	end

	return r
end

local function makeContacts( country )
	local t = {}
	local s = ''

	mu.tableInsert( t, formatPhone( 'phone', country ) )
	mu.tableInsert( t, formatPhone( 'tollfree', country ) )
	mu.tableInsert( t, formatPhone( 'mobile', country ) )
	mu.tableInsert( t, formatPhone( 'fax', country ) )
	if vp.ParMap.email ~= '' then
		local lm = require( 'Module:LinkMail' )
		s = makeSpan( lm.linkMailSet( { email = vp.ParMap.email, ignoreUnicode = 1 } ),
			'u-email email listing-email' .. addWdClass( 'email' ) )
		mu.tableInsert( t, mw.ustring.format( mi.texts.email, s ) )
	end
	if vp.ParMap.skype ~= '' then
		local ls = require( 'Module:LinkSkype' )
		s = makeSpan( ls.linkSkypeSet( { skype = vp.ParMap.skype } ),
			'listing-skype' .. addWdClass( 'skype' ) )
		mu.tableInsert( t, mw.ustring.format( mi.texts.skype, s ) )
	end

	s = table.concat( t, ', ' )
	if s ~= '' then
		s = mw.ustring.gsub( s .. '.', '^%a', mw.ustring.upper )
	end
	return s
end

-- making subtypes
local function makeFeatures( tab, show )
	local count, f, s, t, u, v
	local unknowWDfeatures = false

	vp.wdContent.subtypeAdd = not show.nowdsubtype and
		type( vp.ParMap.subtypeAdd ) == 'table' and #vp.ParMap.subtypeAdd > 0
	if show.nosubtype or not ( vp.wdContent.subtypeAdd or mu.isSet( vp.ParMap.subtype ) ) then
		return
	end

	local vs = mw.loadData( 'Module:VCard/Subtypes' )

	local subtypes = {}
	if mu.isSet( vp.ParMap.subtype ) then
		subtypes, t = mu.splitAndCheck( vp.ParMap.subtype, vs.f, mu.getAliases( vs.f, 'alias' ) )
		if t ~= '' then
			mu.addMaintenance( mw.ustring.format( mi.maintenance.unknownSubtype, t ) )
		end
	end

	-- merging subtypeAdd (from Wikidata) to manually entered subtypes
	if vp.wdContent.subtypeAdd then
		-- making translation table from Wikidata ids to feature types
		local qt = mu.getAliases( vs.f, 'wd' )

		-- adding type if Wikidata id (wd.value) is known
		-- indexed array prevents multiple identical types
		for _, wd in ipairs( vp.ParMap.subtypeAdd ) do
			if qt[ wd.value ] then
				subtypes[ qt[ wd.value ] ] = ( wd[ mi.properties.quantity ]
					and wd[ mi.properties.quantity ][ 1 ] )
					or ( wd[ mi.properties.capacity ] and
					wd[ mi.properties.capacity ][ 1 ] ) or ''
			elseif not vs.exclude[ wd.value ] then
				unknowWDfeatures = true
			end
		end
	end
	if unknowWDfeatures then
		mu.addMaintenance( mi.maintenance.unknowWDfeatures )
	end

	-- sorting subtypes
	-- first alphabetically
	s = {};
	-- make table sortable
	for subtype, count in pairs( subtypes ) do
		table.insert( s, { t = subtype, c = count } )
	end
	table.sort( s,
		function( a, b )
			return mu.convertForSort( vs.f[ a.t ].n ) < mu.convertForSort( vs.f[ b.t ].n )
		end
	)
	-- second by subtype groups
	subtypes = {}
	local data = {}
	for i = 1, vs.n, 1 do -- number of subtype groups
		for _, subtype in ipairs( s ) do
			if vs.f[ subtype.t ] and vs.f[ subtype.t ].g == i then
				table.insert( subtypes, subtype )

				-- for data-subtype="..." in wrapper tag
				u = subtype.t .. ',' .. tostring( i )
				if type( subtype.c ) == 'number' and 
					( subtype.c > 1 or vs.f[ subtype.t ].one ) then
					u = u .. ',' .. subtype.c
				end
				table.insert( data, u )
			end
		end
		if i == vs.addTypesTo and vp.ParMap.typeTable then
			for _, aType in ipairs( vp.ParMap.typeTable ) do
				u = mu.getTypeParams( aType )
				mu.tableInsert( subtypes, u.subtype )
			end
		end
	end
	vp.ParMap.subtype = table.concat( data, ';' )

	-- make text output
	if #subtypes > 0 then
		s = {};
		for _, subtype in ipairs( subtypes ) do
			if type( subtype ) == 'string' then
				v = subtype
			else
				u = vs.f[ subtype.t ]
				if u.g >= vs.first then
					local u_n = u.n
					if not mu.isSet( u_n ) then
						u_n = subtype.t 
					end
					u_n = u_n:gsub( '[,;/].*$', '' )
					count = ( type( subtype.c ) == 'number' ) and subtype.c or 1
					if count > 1 and u_n:find( '%[[^%[%]]*%]' ) then
						v = mw.ustring.format( mi.texts.subtypeWithCount, subtype.c,
							u_n:gsub( '%[([^%[%]]*)|([^%[%]]*)%]', '%1' )
								:gsub( '%[([^%[%]]*)%]', '%1' ) )
					else
						v = u_n:gsub( '%[([^%[%]]*)|([^%[%]]*)%]', '%2' )
							:gsub( '%[([^%[%]]*)%]', '' )
						if u.one then
							v = mw.ustring.format( mi.texts.subtypeWithCount, 1, v )
						end
					end
					if mu.isSet( u.s ) then
						f = u.rp and mw.ustring.rep( u.s, count ) or u.s
						v = mw.ustring.format( mi.texts.subtypeSpan, v, f )
					elseif mu.isSet( u.f ) then
						f = mw.ustring.format( mi.texts.subtypeFile, u.f, v )
						if u.rp then
							f = mw.ustring.rep( f, count )
						end
						v = mw.ustring.format( mi.texts.subtypeAbbr, v, f )
					end
				end
			end
			table.insert( s, v )
		end
		if #s > 0 then
			s = #s == 1 and mw.ustring.format( mi.texts.subtype, s[ 1 ] )
				or mw.ustring.format( mi.texts.subtypes, table.concat( s, ', ' ) )
			if s ~= '' then
				table.insert( tab, makeSpan( s, 
					'listing-subtype' .. addWdClass( 'subtypeAdd' ) ) )
			end
		end
	end
end

local function makePayment( tab )
	if not mu.isSet( vp.ParMap.payment ) then
		return
	end

	local t
	local class = 'p-note note listing-payment'
	if type( vp.ParMap.payment ) == 'table' then
		local vr = mw.loadData( 'Module:VCard/Cards')
		for i = #vp.ParMap.payment, 1, -1 do -- remove unknown items
			t = vp.ParMap.payment[ i ]
			if vr[ t ] then
				vp.ParMap.payment[ i ] = vr[ t ]
			else
				table.remove( vp.ParMap.payment, i )
			end
		end
		if #vp.ParMap.payment > 0 then
			class = class .. ' wikidata-content'
		end
		vp.ParMap.payment = table.concat( vp.ParMap.payment, ', ' )
	else
		mu.addMaintenance( mi.maintenance.paymentUsed )
	end
	formatText( tab, 'payment', class )
end

local function wrapDescription( tab, isInline, addText )
	local text = vp.ParMap.description .. ( addText and ' ' .. addText or '' )
	if vp.ParMap.description ~= '' then
		table.insert( tab, tostring( mw.html.create( isInline and 'span' or 'div' )
			:attr( 'class', 'p-note note listing-content' )
			:wikitext( text ) )
		)
	end
end

local function makeMetadata()
	local outdated = false
	local s, success, u
	local t = vp.ParMap.lastedit
	if t ~='' then
		success, t = pcall( formatDate, t, mi.dates.lastedit.f )
		if not success then
			mu.addMaintenance( mi.maintenance.wrongDate )
			t = ''
		else
			success, s = pcall( formatDate, vp.ParMap.lastedit, 'U' ) -- UNIX seconds
			success, u = pcall( formatDate, mi.texts.expirationPeriod, 'U' )
			if s < u then
				t = t .. ' ' .. mi.texts.maybeOutdated
				outdated = true
			end
		end
	end

	local tag = mw.html.create( 'span' )
		:attr( 'class', 'listing-metadata listing-metadata-items' )
		:node( mw.html.create( 'span' )
			:attr( 'class', 'listing-metadata-item listing-lastedit' )
			:addClass( outdated and 'listing-outdated' or nil )
			:css( 'display', t == '' and 'none' or nil )
			:node( mw.html.create( 'span' )
				:wikitext( mw.ustring.format( mi.texts.lastedit,
					t == '' and mi.texts.lasteditNone or t ) )
			)
		)
	return tostring( tag )
end

-- making description, coordinates, and meta data
local function makeDescription( show, page, country, entity )
	local tab = {}

	-- inline description
	if show.inlineDescription then
		wrapDescription( tab, true )
	end

	-- adding features
	makeFeatures( tab, show )

	-- practicalities
	formatText( tab, 'hours', 'p-note note listing-hours' )
	formatText( tab, 'checkin', 'listing-checkin' )
	formatText( tab, 'checkout', 'listing-checkout' )
	formatText( tab, 'price', 'p-note note listing-price' )
	makePayment( tab )

	-- adding Unesco symbol
	if vp.ParMap.unesco ~= '' then
		table.insert( tab, makeSpan( getUnescoImage( country.country, country.cont ),
			'listing-unesco wv-symbol-inline wv-symbol-unesco', nil, true )	)
	end

	local noContent = #tab == 0

	-- adding DMS coordinates
	if show.coord then
		table.insert( tab, mu.dmsCoordinates( vp.ParMap.lat, vp.ParMap.long,
			vp.ParMap.givenName.name, vp.wdContent.lat, country.extra, true ) )
	end
	if mi.options.showSisters == 'atEnd' then
		table.insert( tab, makeIcons( page, country, entity, body ~= '' ) )
	end

	local metaData = makeMetadata()

	-- adding description in block mode
	local description
	if vp.ParMap.description ~= '' and not show.inlineDescription then
		-- last edit will be inserted at the end of the div tag
		wrapDescription( tab, false, metaData )
		noContent = false
		description = table.concat( tab, ' ' )
		if description ~= '' then
			description = ' ' .. description
		end
	else
		description = table.concat( tab, ' ' )
		if description ~= '' then
			description = ' ' .. description
		end
		description = description .. metaData
	end

	return removeFullStops( description ), noContent
end

-- vCard main function
function vc.vCard( frame )
	mu.initMaintenance( mi.moduleNames.vcard )
	local page = pd.getPageData()

	-- getting location (vCard/listing) entity, show options and country data
	local defaultType = frame.args.type
	local vcEntity, show, country = initialParameterCheck( frame:getParent().args )

	-- associated Wikivoyage page of the location in current Wikivoyage branch
	-- possibly modified by mu.getArticleLink()
	vp.ParMap.wikiPage = ''

	-- getting data from Wikidata
	getDataFromWikidata( page, country, vcEntity )

	-- final check
	finalParameterCheck( show, page, country, defaultType, vcEntity )

	-- making output
	-- leading part for marker mode: only location names and comment

	-- saving address
	vp.ParMap.addressOrig = vp.ParMap.address

	-- creating text parts
	-- leading part (marker and names)
	local leading = makeMarkerAndName( show, page, country, frame )
		.. makeEvent( page )

	-- additional parts for vCard mode
	local contacts = '' -- all contacts

	-- get address and directions
	local address = makeAddressAndDirections( page, country )

	-- get all contact information
	local contacts = removeFullStops( address .. makeContacts( country ) )
	
	-- making description, coordinates, and meta data
	local description, noContent =
		makeDescription( show, page, country, vcEntity )

	local r = leading
	if contacts == '' and noContent then
		show.inline = true
		local icons = makeIcons( page, country, vcEntity, false )
		if icons ~= '' then
			icons = ' ' .. icons
		end
		r = r .. icons .. description
	else
		r = r .. ( address == '' and '. ' or '' ) .. contacts
		if type( mi.options.showSisters ) == 'boolean' and mi.options.showSisters then
			-- could also be 'atEnd', then part of body
			r = r .. makeIcons( page, country, vcEntity, true )
		end
		r = r .. description
	end

	-- wrapping tag
	vp.ParMap.address = vp.ParMap.addressOrig
	r = mu.makeWrapper( r, vp.ParMap, page, country, show, vi.vcardData, 'vCard', frame )

	-- error handling and maintenance, not in template and module namespaces
	if country.unknownLanguage then
		mu.addMaintenance( mi.maintenance.unknownLanguage )
	end

	local ns = page.namespace
	if ns ~= 4 and ns ~= 10 and ns ~= 828 then
		r = r .. mu.getMaintenance()
		if mi.options.usePropertyCateg then
			local m = mi.maintenance.properties -- format string
			r = r .. wu.getCategories( m ) .. mu.getCategories( m )
				.. cm.getCategories( m )
			if hr then
				r = r .. hr.getCategories( m )
			end
		end
	end

	return r
end

return vc