Modul:VCard

Aus Wikivoyage
Zur Navigation springen Zur Suche springen
Template-info.png Dokumentation für das Modul VCard[Ansicht] [Bearbeiten] [Versionsgeschichte] [Aktualisieren]

Anmerkungen

Versionsbezeichnung auf WikiData: 2020-07-06

Anwendung

Das Modul wird direkt von der Vorlage {{vCard}} aufgerufen. Parameterbeschreibung siehe dort. Im Projektnamensraum befindet sich die technische Dokumentation.

Experimentalbeispiele

* {{vCard | name= Ein Hotel | type = hostel | url= http://hotel.de | lat = 52.5144 | long = 13.389722 | show = all, subtype | 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 {{Abk|ÜF}}. | phone = +49 (0)30 2345 1234 | fax = +49 (0)30 2345 9876, +49 0176 345 1234 | before = {{flagicon|Deutschland}} | image = Berlin Friedrichstraße Galeries Lafayette.jpg | email = info@hotel.de | comment = j | lastedit = 2016-11-17 | hours = 7/24. | checkin = ab 14 Uhr | checkout = bis 12 Uhr | payment = Visa, Master, AmEx, Maestro | subtype = wlan, bar, pool | skype = nutzer.name; nutzer2.name }}

* {{vCard | wikidata = Q201219 | auto = j | name = [[Kairo/Ägyptisches Museum|Ä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 = [[Kairo/Ägyptisches Museum|Ä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 = icon | description = Test Flughafen mit Ikone. }}
* {{vCard | wikidata = Q13218762 | auto = j | description = Anzeige englische Wikipedia. }}
* {{vCard | wikidata = Q47429618 | auto = j | description = Ausgabe Anschriften. | show = subtype | payment = Visa, Master }}
* {{vCard | wikidata = Q47429618 | address = | address-local = | directions = | phone = | description = Test eingeschränkte WD-Ausgabe. }}
* {{vCard | wikidata = Q637739 | auto = j | show = subtype | description = Rollstuhl aus WD. }}
  • DeutschlandFlagge Deutschland 1 Ein Hotel, Hauptstraße 1 (Abzweig von der Nebenstraße). Tel.: +49 (0)30 2345 1234, Fax: +49 (0)30 2345 9876, +49 (0)176 345 1234, E-Mail: , Skype: nutzer.name, nutzer2.name. Das in der Innenstadt gelegene Hotel besitzt großzügige Räume, bietet aber nur ÜF. Geöffnet: 7/24. Check-in: ab 14 Uhr. Check-out: bis 12 Uhr. Akzeptierte Zahlungsarten: Visa, Master, AmEx, Maestro. Merkmale: WLAN, Bar, Schwimmbecken. (52° 30′ 52″ N 13° 23′ 23″ O)

Parameter show

  • Mögliche Werte: none, poi, coord, all, subtype, nosubtype, indent, inline. 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.
    • Zukünftig bewirkt indent im Blockmodus einen eingerückten Absatz mit hängender erster Zeile. Das POI-Symbol wirkt dann wie ein Aufzählungszeichen.

Benötigte weitere Module

Dieses Modul benötigt folgende weitere Module: CountryData • Failsafe • FastWikidata • Languages • LinkMail • LinkPhone • LinkSkype • MarkerBase • MarkerBase/i18n • MarkerBase/Maki icons • VCard/Cards • VCard/Defaults • VCard/i18n • VCard/Params • VCard/Subtypes • Yesno
Hinweise
-- documentation
local vCard = {
	suite  = 'vCard',
	serial = '2020-07-06',
	item   = 58187507
}

-- module import
local mb = require( 'Module:MarkerBase' )
local mi = require( 'Module:MarkerBase/i18n' )
local mm = require( 'Module:MarkerBase/Maki icons' )
local vp = require( 'Module:VCard/Params' ) -- parameter lists
local vi = require( 'Module:VCard/i18n' ) -- parameter translations
local vs = require( 'Module:VCard/Subtypes' ) -- features of a location
local vr = mw.loadData( 'Module:VCard/Cards') -- payment types
local vd = mw.loadData( 'Module:VCard/Defaults' ) -- country-specific show defaults

local cm = require( 'Module:CountryData' )
local fs = require( 'Module:Failsafe' )
local fw = require( 'Module:FastWikidata' )
local lg = require( 'Module:Languages' )
local lp = require( 'Module:LinkPhone' )
local lm = require( 'Module:LinkMail' )
local ls = require( 'Module:LinkSkype' )
local yn = require( 'Module:Yesno' )

-- module variable
local vc = {}

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, translKey, v

	-- force getting data from Wikidata for missing parameters
	show.descrDiv = false -- 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, value in pairs( args ) do
		translKey = nil
		for key2, value2 in pairs( vi.p ) do
			if value2 == key then
				translKey = key2
				break
			end
		end

		v, t =
			mb.removeCtrls( value, show.inline or translKey ~= 'description' )
		if t then
			show.descrDiv = true
		end

		if translKey then
			if translKey ~= 'auto' and translKey ~= 'show'
				and translKey ~= 'wikidata' then
				if v == '' then
					v = true
				end
				t = yn( v, nil )
				-- yn takes '0' as false

				if t ~= nil and v ~= '0' then
					if vp.ParMap.wikidata ~= '' then
						vp.ParMap[ translKey ] = t
					else
						vp.ParMap[ translKey ] = ''
					end
				else
					vp.ParMap[ translKey ] = v
				end
			end
		else
			mb.addWrongParameter( key )
		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

	-- remove namespace from category
	if vp.ParMap.commonscat ~= '' then
		vp.ParMap.commonscat = mb.checkCategory( vp.ParMap.commonscat )
		if vp.ParMap.commonscat ~= '' then
			mb.addMaintenance( mi.maintenance.commonscat )
		end
	end
end

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

	vp.ParMap.wikidata, entity, wrongQualifier = fw.getEntity( args[ vi.p.wikidata ] or '' )
	if wrongQualifier then
		mb.addMaintenance( mi.maintenance.wrongQualifier )
	elseif vp.ParMap.wikidata ~= '' then
		mb.addMaintenance( mi.maintenance.wikidata )
	end
	if vp.ParMap.wikidata ~= '' and args[ vi.p.auto ] then
		if args[ vi.p.auto ] == '' then
			args[ vi.p.auto ] = 'y'
		end
		vp.ParMap.auto = yn( args[ vi.p.auto ], false )
	else
		vp.ParMap.auto = false
	end

	-- getting country-specific technical parameters
	country = cm.getCountryData( entity )
	if country.fromWD then
		mb.addMaintenance( mi.maintenance.countryFromWD )
	end
	if country.lang ~= '' then
		country.langName = lg.getProperty( country.lang, 'n' )
		country.R2L = lg.isR2L( country.lang )
		country.unknownLanguage = country.langName == ''
	else
		country.unknownLanguage = true
	end
	country.extra = mi.defaultSiteType
	if country.iso_3166 ~= '' then
		country.extra = country.extra .. '_region:' .. country.iso_3166
		-- country-specific default show
		t = vd[ country.iso_3166 ]
		if t then
			vp.ParMap.show = t
		end
	end

	-- handling args and vp.ParMap show arrays
	show = mb.getShow( vp.ParMap.show, args[ vi.p.show ], vp.show )
	if args[ vi.p.show ] and args[ vi.p.show ]:find( 'inline', 1, true ) then
		mb.addMaintenance( mi.maintenance.inlineSelected )
	end
	if show.nosubtype then
		show.subtype = nil	
	end
	if show.subtype then
		vp.ParMap.subtypeAdd = not args[ vi.p.subtypeAdd ]
	end
	show.name = true

	-- copying args parameters to vp.ParMap parameters
	copyParameters( args, show )

	return entity, show, country
end

-- p : property id
-- tp: data value type
-- mx: maximum count
-- f : format
-- l = true: language dependent
-- l = wiki / local: monolingual text by wiki or local language

-- function returns an array in any case
local function getWikidataValues( p, tp, mx, f, l, entity, country )
	local r = ''
	local ar = {}
	local a, i, t, u, w

	-- getting value arrays
	if l == 'wiki' then
		ar = fw.getValuesByLang( entity, p, mx, vp.ParMap.wikiLang )
	elseif l == 'local' then
		ar = fw.getValuesByLang( entity, p, mx, country.lang )
	elseif l == true and mx == 1 then
		u = lg.getProperty( country.lang, 'q' )
		if u == '' then
			country.unknownLanguage = true
		else
			-- P407: language of work or name
			a = fw.getValuesWithQualifierIds( entity, p, 'P407', u )
			if next( a ) then
				i = a[ lg.getProperty( vp.ParMap.wikiLang, 'q' ) ] -- item in wikiLang
				if not i then
					i = a[ u ] -- item in country lang
					if not i then
						i = a[ next( a, nil ) ] -- first item
					end
				end
				ar = { i }
			end
		end
	else
		ar = fw.getValues( entity, p, mx )
	end
	if #ar == 0 then
		return ar
	end

	if p == 'P31' then -- instance of
		p = mb.typeSearch( ar )
		return { p }
	end

	for i = #ar, 1, -1 do
		-- amount with unit
		if tp == 'au' then
			a = ar[ i ].amount
			a = a:gsub( '+', '' )
			if mi.texts.decimalPoint ~= '.' then
				a = a:gsub( '%.', mi.texts.decimalPoint )
			end
			u = ar[ i ].unit
			u = u:gsub( 'http://www.wikidata.org/entity/', '' )
			if u:match( '^Q[%d]+$' ) then
				-- P498: currency code
				t = fw.getValue( u, 'P498' )
				if t == '' then
					-- P5061: unit symbol
					t = fw.getValuesByLang( u, 'P5061', 1, vp.ParMap.wikiLang )
					t = table.concat( t )
				end
				u = t
			else
				u = ''
			end
			if a ~= '' and u ~= '' and f ~= '' and f:find( '$1', 1, true )
				and f:find( '$2', 1, true ) then
				a = mw.ustring.gsub( f, '($1)', a )
				a = mw.ustring.gsub( a, '($2)', u )
			else
				a = a .. ' ' .. u
			end
			ar[ i ] = mw.text.trim( a )

		-- value, monolingual text, identifier
		else
			if tp == 'id' then
				ar[ i ] = ar[ i ].id
			end
			if ar[ i ] ~= '' and f ~= '' and f:find( '$1', 1, true ) then
				ar[ i ] = mw.ustring.gsub( f, '($1)', ar[ i ] )
			end
		end
		if ar[ i ] == '' then
			table.remove( ar, i )
		end
	end
	return ar
end

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

	local function singleProperty( pr, f, c, v, l )
		if #arr == 0 then
			arr = getWikidataValues( pr, v or '', c or 1, f or '', l, entity, country )
		else
			subArr = getWikidataValues( pr, v or '', c or 1, f or '', l, entity, 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.p, p.f, p.c, p.v, p.l )
	elseif tp == 'table' then
		for key, value in pairs( p.p ) do
			if type( value ) == 'table' then
				singleProperty( value.p, value.f, value.c, value.v, value.l )
			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

-- getting names from Wikidata
local function getNamesFromWikidata( country, entity )
	-- getting official names
	vp.ParMap.officialNames =
		fw.getValuesWithLanguages( entity, 'P1448' )

	if vp.ParMap.name == true then
		vp.ParMap.name = vp.ParMap.officialNames[ vp.ParMap.wikiLang ]
			or mi.langs.name ~= '' and vp.ParMap.officialNames[ mi.langs.name ]
			or ''
		-- if failed then get labels
		if vp.ParMap.name == '' then
			if vp.ParMap.wikiLang == mi.langs.name then
				vp.ParMap.name = fw.getLabel( entity ) or ''
			else
				vp.ParMap.name = fw.getLabel( entity )
					or mi.langs.name ~= '' and fw.getLabel( entity, mi.langs.name )
					or ''
			end
		end
		if vp.ParMap.name ~= '' then
			mb.addMaintenance( mi.maintenance.nameFromWD )
			vp.wdContent.name = true
		end
	end

	if vp.ParMap.nameLocal == true then
		if vp.ParMap.officialNames then
			vp.ParMap.nameLocal = vp.ParMap.officialNames[ country.lang ] or ''
		end
		if vp.ParMap.nameLocal == '' then
			vp.ParMap.nameLocal = fw.getLabel( entity, country.lang ) or ''
		end
		vp.wdContent.nameLocal = vp.ParMap.nameLocal ~= ''
	end
end

local function getAddressesFromWikidata( 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 = fw.getValuesWithLanguages( entity, 'P6375' )
		if next( addresses ) then -- sometimes addresses contain <br> tag(s)
			for key, value in pairs( addresses ) do
				addresses[ key ] = mw.ustring.gsub( value, '</*br%s*/*>', ' ' )
			end
		else
			return
		end
	else
		return
	end

	if vp.ParMap.address == true then
		vp.ParMap.address = addresses[ vp.ParMap.wikiLang ]
		-- select address if the same writing system is used
		if not vp.ParMap.address then
			weight = -1
			u = lg.getProperty( vp.ParMap.wikiLang, 'w' ) -- writing entity id
			for key, value in pairs( addresses ) do
				-- same writing entity id as vp.ParMap.wikilang
				w = lg.getProperty( key, 'w' )
				if w == '' then
					country.unknownLanguage = true
				else
					if key and w == u then -- same writing entity id
						w = lg.getProperty( 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 i = 1, #mi.langs.address, 1 do
				t = mi.langs.address[ i ]
				if addresses[ t ] then
					vp.ParMap.address = addresses[ t ]
					vp.ParMap.addressLang = t
					break
				end
			end
		end
		if not vp.ParMap.address then
			vp.ParMap.address = ''
			vp.ParMap.addressLang = ''
		end
			vp.wdContent.address = vp.ParMap.address ~= ''
	end

	t = true
	for i = 1, #mi.langs.address, 1 do
		if country.lang == mi.langs.address[ i ] then
			t = false
		end
	end
	if t and vp.ParMap.addressLocal == true
		and country.lang ~= vp.ParMap.wikiLang 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( country, entity )
	local s, t

	-- image check
	if type( vp.ParMap.image ) ~= 'boolean' and vp.ParMap.image ~= '' then
		vp.ParMap.image = mb.checkImage( vp.ParMap.image, entity )
	end

	if vp.ParMap.wikidata ~= '' then
		-- except local data if wiki language == country language
		if vp.ParMap.wikiLang == 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

		getNamesFromWikidata( country, entity )
		getAddressesFromWikidata( country, entity )

		for key, value in pairs( vp.ParWD ) do
			if vp.ParMap[ key ] == true then
				vp.ParMap[ key ] =
					getFromWikidata( vp.ParWD[ key ], entity, country )
				vp.wdContent[ key ] = vp.ParMap[ key ] ~= ''
				if key == 'type' then
					mb.addMaintenance( mi.maintenance.typeFromWD )
				end
			end
		end

		-- getting wiki links
		t = fw.getSitelink( entity, vp.ParMap.wikiLang .. 'wikivoyage' )
		if t and t ~= vp.ParMap.titleObj.text then  -- no link to the article itself
			vp.ParMap.thisWiki = t
		end

		-- getting commonscat from commonswiki sitelink before P373
		-- because sitelink is checked by Wikidata
		t = ''
		s = fw.getSitelink( entity, 'commonswiki' ) or ''
		if mw.ustring.find( s, 'Category:', 1, true ) then
			t = mw.ustring.gsub( s, 'Category:', '' )
		end
		if t == '' then	
			t = fw.getValue( entity, 'P373' )
		end
		if type( vp.ParMap.commonscat ) == 'boolean' then
			vp.ParMap.commonscat = ''
		end
		if t ~= '' and vp.ParMap.commonscat ~= '' then
			mb.addMaintenance( mi.maintenance.commonscatWD )
		end
		if vp.ParMap.commonscat == '' then
			vp.ParMap.commonscat = t
		end
	end

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

	-- checking and getting coordinates
	vp.ParMap.lat, vp.ParMap.long =
		mb.checkCoordinates( vp.ParMap.lat, vp.ParMap.long )
	if ( vp.ParMap.lat == '' or vp.ParMap.long == '' ) and vp.ParMap.wikidata ~= '' then
		t = mb.getCoordinatesFromWikidata( entity )
		if t ~= '' then
			vp.ParMap.lat = t.latitude
			vp.ParMap.long = t.longitude
			vp.wdContent.lat = ''
		end
	end
end

local function compareLocal( value1, key2 )
	if value1 ~= '' and vp.ParMap[ key2 ] ~= '' and
		mw.ustring.upper( value1 ) == mw.ustring.upper( vp.ParMap[ key2 ] ) then
		vp.ParMap[ key2 ] = ''
	end
end

local function finalParameterCheck( show, country )
	-- 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
		mb.addMaintenance( mi.maintenance.missingNameMsg )
	end
	-- handling linked names like [[article|text]]
	vp.ParMap.givenName = mb.getName( vp.ParMap.name, vp.ParMap.thisWiki )

	-- 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 =
			mb.languageSpan( vp.ParMap.addressLocal, mi.texts.hintAddress, vp.ParMap, 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
		mb.addMaintenance( mi.maintenance.malformedName )
	end

	-- error handling of subtype array
	local subtypes, t = mb.splitAndCheck( vp.ParMap.subtype, vs.f )
	if t ~= '' then
		mb.addMaintenance( mw.ustring.format( mi.maintenance.unknownSubtype, t ) )
	end

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

	-- getting Marker type and group
	vp.ParMap.type, vp.ParMap.group = mb.checkTypeAndGroup( vp.ParMap.type, vp.ParMap.group )
	vp.ParMap.groupTranslated = mb.translateGroup( vp.ParMap.group )

	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 = mb.getColor( vp.ParMap.group )

	if vp.ParMap.commonscat ~= '' then
		vp.ParMap.commonscat = mw.ustring.gsub( vp.ParMap.commonscat, ' ', '_' )
	end

	return subtypes
end

local function formatText( key, class )
	if vp.ParMap[ key ] == '' then
		return ''
	end

	local r = mw.ustring.format( mi.texts[ key ], vp.ParMap[ key ] )
	if not r:find( '%.$' ) then
		r = r .. '.'
	end
	
	return ' <span class="' .. class .. addWdClass( key ) .. '">' .. r .. '</span>'
end

local function formatPhone( key, country )
	if vp.ParMap[ key ] == '' then
		return ''
	end
	
	local t
	local pArgs = {
		phone = vp.ParMap[ key ],
		cc = country.cc,
		nocc = true,
		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
		t = '<span class="p-tel-fax fax listing-fax'
		pArgs.isFax = true
	else
		t = '<span class="p-tel tel listing-phone'
		if key == 'phone' then
			t = t .. ' listing-landline'
		elseif key == 'tollfree' then
			t = t .. ' listing-tollfree'
			pArgs.isTollfree = true
		elseif key == 'mobile' then
			t = t .. ' listing-mobile'
		end
	end
	t = t .. addWdClass( key ) .. '">' .. lp.linkPhoneNumberSet( pArgs ) .. '</span>'
	return mw.ustring.format( mi.texts[ key ], t )
end

local function makeIcons( country, entity, withFullStop )
	local r = ''
	local name = vp.ParMap.givenName.name
	local t, s = mb.makeSisterIcons( vp.ParMap, country, entity )
	local u = mb.makeSocial( vp.ParMap, vp.wdContent, name )
	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 mw.ustring.gsub( t, '</span>', '.</span>' )
	end
	if u ~= '' then -- with social media
		return t .. '<span class="listing-social-media listing-full-stop">.</span>'
	else
		return t .. '<span class="listing-sister-icon listing-full-stop">.</span>'
	end
end

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

local function removeFullStops( s )
	-- closing (span) tags between full stops
	s = mw.ustring.gsub( s, '%.+(</[%l<>/]+>)%.+', '%1.' )
	return mw.ustring.gsub( s, '%.%.+', '.' )
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, country, frame )
	local r = ''
	local s, t

	if vp.ParMap.before ~= '' then
		r = r .. vp.ParMap.before .. '&#160;'
	end

	-- adding POI marker
	if show.poi then
		vp.ParMap.symbol = '-number-' .. vp.ParMap.group
		if show.icon then
			s = mb.getMakiIconName( vp.ParMap.type )
			if s then
				vp.ParMap.symbol = s
				vp.ParMap.text = mw.ustring.format( '[[File:%s|x14px|link=|class=noviewer]]',
					mm[ s ].im )
			end
		end
		vp.ParMap.useIcon = false
		r = r ..
			mb.makeMarkerSymbol( vp.ParMap, vp.ParMap.givenName.all, frame ) .. ' '
	end
	
	-- adding names, url, comment and airport codes
	-- url check
	vp.ParMap.url = mb.checkUrl( vp.ParMap.url )
	s = '<bdi id="vCard_' .. mw.uri.anchorEncode( vp.ParMap.givenName.name )
		.. '" class="p-name p-org fn org listing-name' .. addWdClass( 'name' )
		.. '">'
	t = ''
	if vp.ParMap.nameExtra ~= '' then
		t = ' ' .. vp.ParMap.nameExtra
	end
	if vp.ParMap.url ~= '' then
		if vp.ParMap.givenName.pageTitle == '' then
			s = s .. '[' .. vp.ParMap.url .. ' '
				.. mb.replaceBrackets( vp.ParMap.givenName.name )
				.. ']' .. t .. '</bdi>'
		else
			-- article and web links
			s = s .. vp.ParMap.givenName.all .. t
				.. '</bdi> <span class="listing-url">['
				.. vp.ParMap.url .. ' ' .. mi.icons.internet .. ']</span>'
		end
	else
		s = s .. vp.ParMap.givenName.all .. t .. '</bdi>'
	end
	r = r .. s

	local tab = {}
	if mi.options.showLocalData then
		if vp.ParMap.nameLocal ~= '' then
			mb.tableInsert( tab, '<span class="listing-name-local' .. addWdClass( 'nameLocal' ) .. '">'
				.. mb.languageSpan( vp.ParMap.nameLocal, mi.texts.hintName, vp.ParMap, country )
				.. '</span>' )
		end
		if vp.ParMap.nameLatin ~= '' then
			mb.tableInsert( tab, '<span class="listing-name-latin listing-emphasized" title="'
				.. mi.texts.hintLatin .. '">' .. vp.ParMap.nameLatin .. '</span>' )
		end
	end
	if vp.ParMap.alt ~= '' then
		mb.tableInsert( tab, '<bdi>' .. vp.ParMap.alt .. '</bdi>' )
	end
	t = table.concat( tab, ', ' )
	if t ~= '' then
		t = '<span class="p-nickname nickname listing-alt">' .. t .. '</span>'
	end
	if t ~= '' then
		tab = { t }
	else
		tab = {}
	end
	if vp.ParMap.comment ~= '' then
		table.insert( tab, '<bdi class="listing-comment listing-emphasized">'
			.. vp.ParMap.comment .. '</bdi>' )
	end
	if not show.noairport and vp.ParMap.type == mi.airportType then
		mb.tableInsert( tab, mb.makeAirport( vp.ParMap, vp.wdContent ) )
	end
	t = table.concat( tab, ', ' )
	if t ~= '' then
		r = r .. ' (' .. t .. ')'
	end

	return r
end

local function makeAddressAndDirections( country )
	local r = ''

	if vp.ParMap.address ~= '' then
		r = r .. ', <bdi class="p-adr adr listing-address'
			.. addWdClass( 'address' ) .. '"'
		if vp.ParMap.addressLang and vp.ParMap.addressLang ~= '' then
			t = lg.getProperty( vp.ParMap.addressLang, 'n' )
			if t == '' then
				country.unknownLanguage = true
			end
			r = r .. ' lang="' .. vp.ParMap.addressLang .. '"'
				.. ' title="' .. mw.ustring.format( mi.texts.hintAddress2, t ) .. '"'
		end
		r = r .. '><span class="p-street-address street-address">'
			.. vp.ParMap.address .. '</span></bdi>'
	end
	if mi.options.showLocalData and vp.ParMap.addressLocal ~= '' then
		r = r .. '<span class="listing-add-info">, <span class="listing-address-local'
			.. addWdClass( 'addressLocal' ) .. '">'
			.. vp.ParMap.addressLocal
			.. '</span></span>'
	end

	local t = ''
	if vp.ParMap.directions ~= '' then
		t = '<span class="listing-directions listing-emphasized'
			.. addWdClass( 'directions' ) .. '">' .. vp.ParMap.directions
			.. '</span>'
	end
	local s = ''
	if mi.options.showLocalData and vp.ParMap.directionsLocal ~= '' then
		s = '<span class="listing-directions-local'
			.. addWdClass( 'directionsLocal' ) .. '">'
			.. mb.languageSpan( vp.ParMap.directionsLocal, mi.texts.hintDirections, vp.ParMap, country )
			.. '</span>'
		if t ~= '' then
			s = ', ' .. s
		end
		s = '<span class="listing-add-info">' .. s .. '</span>'
	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 = ''

	mb.tableInsert( t, formatPhone( 'phone', country ) )
	mb.tableInsert( t, formatPhone( 'tollfree', country ) )
	mb.tableInsert( t, formatPhone( 'mobile', country ) )
	mb.tableInsert( t, formatPhone( 'fax', country ) )
	if vp.ParMap.email ~= '' then
		s = '<span class="u-email email listing-email' .. addWdClass( 'email' )
			.. '">' .. lm.linkMailSet( { email = vp.ParMap.email } ) .. '</span>'
		mb.tableInsert( t, mw.ustring.format( mi.texts.email, s ) )
	end
	if vp.ParMap.skype ~= '' then
		s = '<span class="listing-skype' .. addWdClass( 'skype' ) .. '">'
			.. ls.linkSkypeSet( { skype = vp.ParMap.skype } ) .. '</span>'
		mb.tableInsert( t, mw.ustring.format( mi.texts.skype, s ) )
	end

	s = table.concat( t, ', ' )
	if s ~= '' then
		s = s .. '.'
	end
	return s
end

-- making description, coordinates, and meta data
local function makeDescription( show, country, subtypes, entity )
	local r = ''
	local s, t, u

	-- inline description
	if vp.ParMap.description ~= '' and not show.descrDiv then
		r = r .. ' <span class="p-note note listing-content">' ..
			vp.ParMap.description .. '</span>'
	end

	-- practicalities
	r = r .. formatText( 'hours', 'p-note note listing-hours' )
		.. formatText( 'checkin', 'listing-checkin' )
		.. formatText( 'checkout', 'listing-checkout' )
		.. formatText( 'price', 'p-note note listing-price' )

	if vp.ParMap.payment ~= '' then
		u = 'p-note note listing-payment'
		if vp.wdContent.payment then -- vp.ParMap.payment is an array
			if #vp.ParMap.payment > 0 then
				u = u .. ' wikidata-content'
				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 ].label
					else
						table.remove( vp.ParMap.payment, i )
					end
				end
			end
			vp.ParMap.payment = table.concat( vp.ParMap.payment, ', ' )
		else
			mb.addMaintenance( mi.maintenance.paymentUsed )
		end
		r = r .. formatText( 'payment', u )
	end

	-- features
	if show.subtype then
		-- merging subtypeAdd to subtypes
		vp.wdContent.subtypeAdd =
			type( vp.ParMap.subtypeAdd ) == 'table' and #vp.ParMap.subtypeAdd > 0
		if vp.wdContent.subtypeAdd then
			for key, value in ipairs( vp.ParMap.subtypeAdd ) do
				if vs.t[ value ] then -- value is Wikidata id
					subtypes[ vs.t[ value ] ] = ''
				end
			end
		end
		
		if next( subtypes ) then
			-- sorting subtypes
			s = {};
			for key, value in pairs( subtypes ) do
				table.insert( s, key )
			end
			table.sort( s )
			subtypes = s

			s = {};
			for i = vs.first, vs.n, 1 do -- number of subtype groups
				for key, value in ipairs( subtypes ) do
					u = vs.f[ value ]
					if u and u.g == i then
						if u.f ~= '' then
							table.insert( s,
								mw.ustring.format( mi.texts.subtypeFormat,
									u.n, u.f, u.n ) )
						elseif u.n ~= '' then
							table.insert( s, u.n )
						else
							table.insert( s, '[' .. value .. ']' )
						end
					end
				end
			end
			s = table.concat( s, ', ' )
			if s ~= '' then
				r = r .. ' <span class="listing-subtype' .. addWdClass( 'subtypeAdd' )
					.. '">' .. mw.ustring.format( mi.texts.subtype, s ) .. '</span>'
			end
		end
	end

	if vp.ParMap.unesco ~= '' then
		r = r .. ' <span class="listing-unesco wv-symbol-inline wv-symbol-unesco">'
			.. getUnescoImage( country.country, country.cont ) .. '</span>'
	end

	local noContent = r == ''

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

	-- adding description in block mode
	if vp.ParMap.description ~= '' and show.descrDiv then
		-- last edit will be inserted at the end of the div tag
		r = r .. '<div class="p-note note listing-content">\n' .. vp.ParMap.description
		noContent = false
	end

	-- adding meta data
	t = ''
	if vp.ParMap.lastedit ~='' then
		local success;
		success, t = pcall( formatDate, vp.ParMap.lastedit, 'M Y' )
		if success then
			t = mw.ustring.format( mi.texts.lastedit, t )
		else
			mb.addMaintenance( mi.maintenance.wrongDate )
			t = ''
		end
	end
	r = r .. ' <span class="listing-metadata listing-metadata-items">'
		.. '<span class="listing-metadata-item listing-lastedit"'
	if t == '' then
		r = r .. ' style="display:none"><span>'
			.. mw.ustring.format( mi.texts.lastedit, mi.texts.lasteditNone )
	else
		r = r .. '><span>' .. t
	end
	r = r .. '</span></span></span>'

	-- closing tag for description in block mode
	if vp.ParMap.description ~= '' and show.descrDiv then
		r = r .. '</div>'
	end

	return removeFullStops( r ), noContent
end

-- vCard main function

function vc.vCard( frame )
	mb.initMaintenance( 'VCard' )

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

	vp.ParMap.titleObj = mw.title.getCurrentTitle()
	vp.ParMap.thisWiki = ''  -- current Wikivoyage branch
	vp.ParMap.wikiLang = mw.getContentLanguage():getCode()

	-- getting data from Wikidata
	getDataFromWikidata( country, vcEntity )
	vp.ParMap.wikiR2L = lg.isR2L( vp.ParMap.wikiLang )

	-- final check, create table of subtypes
	local subtypes = finalParameterCheck( show, country )

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

	-- text parts
	vp.ParMap.addressOrig = vp.ParMap.address

	-- leading part (marker and names)
	local leading = makeMarkerAndName( show, country, frame )

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

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

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

	local r = ''
	if contacts == '' and noContent then
		r = leading
			.. makeIcons( country, vcEntity, false ) .. description
	else
		r = leading .. ( 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( country, vcEntity, true )
		end
		r = r .. description
	end

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

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

	local ns = vp.ParMap.titleObj.namespace
	if ns ~= 4 and ns ~= 10 and ns ~= 828 then
		local m = mi.maintenance.properties
		r = r .. mb.getMaintenance() .. fw.getCategories( m )
			.. mb.getCategories( m ) .. cm.getCategories( m )
	end

	return r
end

-- module administration
function vc.VCard()
	return vCard
end

function vc.failsafe( version )
	return fs._failsafe( version, vCard ) or ''
end

return vc