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-09-18 Ok!

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 = 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 = [[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 = symbol | 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, 89 Zimmer. (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 • PageData • VCard/Cards • VCard/i18n • VCard/Params • VCard/Subtypes • Yesno
Hinweise
-- documentation
local vCard = {
	suite  = 'vCard',
	serial = '2020-09-18',
	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 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 pd = require( 'Module:PageData' )
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.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, 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.inlineDescription = false
		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
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

	-- making phone number table
	t = {}
	for _, key in ipairs( vi.phones ) do
		if vi.p[ key ] then
			mb.tableInsert( t, args[ vi.p[ key ] ] )
		end
	end
	-- getting country-specific technical parameters
	country = cm.getCountryData( entity, t )
	if country.fromWD then
		mb.addMaintenance( mi.maintenance.countryFromWD )
	end
	country.extra = mi.defaultSiteType
	if country.iso_3166 ~= '' then
		country.extra = country.extra .. '_region:' .. country.iso_3166
		-- country-specific default show
	end
	if country.show and country.show ~= '' then
		vp.ParMap.show = country.show
	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
	show.name = true

	-- copying args parameters to vp.ParMap parameters
	copyParameters( args, show )
	-- checking coordinates and converting DMS to decimal coordinates if necessary
	mb.checkCoordinates( vp.ParMap )
	-- remove namespace from category
	mb.checkCommonsCategory( vp.ParMap )
	-- image check
	vp.ParMap.image = mb.checkImage( vp.ParMap.image, entity )

	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, page, country )
	local r = ''
	local ar = {}
	local a, i, id, t, u, w

	-- getting value arrays
	if l == 'wiki' then
		ar = fw.getValuesByLang( entity, p, mx, page.lang )
	elseif l == 'local' then
		ar = fw.getValuesByLang( entity, p, mx, country.lang )
	elseif l == true and mx == 1 then
		id = lg.getProperty( country.lang, 'q' )
		if id == '' then
			country.unknownLanguage = true
		else
			-- language of work or name
			a = fw.getValuesWithQualifierIds( entity, p, mi.properties.languageOfName, id )
			if next( a ) then
				i = a[ lg.getProperty( 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 tp == 'iq' then
		ar = fw.getValuesWithQualifiers( entity, p, mi.properties.quantity, mx )
	else
		ar = fw.getValues( entity, p, mx )
	end
	if #ar == 0 and p ~= mi.properties.instanceOf then
		return ar
	elseif p == mi.properties.instanceOf then -- instance of
		return { mb.typeSearch( ar, entity ) }
	end

	for i = #ar, 1, -1 do
		-- amount with unit
		if tp == 'au' then
			a = ar[ i ].amount:gsub( '%+', '' )
			if mi.texts.decimalPoint ~= '.' then
				a = a:gsub( '%.', mi.texts.decimalPoint )
			end
			u = ''
			id = ar[ i ].unit:gsub( 'http://www.wikidata.org/entity/', '' )
			if mw.wikibase.isValidEntityId( id ) then
				-- currency code
				u = fw.getValue( id, mi.properties.iso4217 )
				if u == '' then
					-- unit symbol
					u = fw.getValuesByLang( id, mi.properties.unitSymbol, 1,
						page.lang )
					u = u[ 1 ] or ''
				end
			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 )

		elseif tp == 'iq' then
			if ar[ i ][ 'value-type' ] ~= 'wikibase-entityid' then
				table.remove( ar, i )
			end

		-- value, monolingual text, identifier
		else
			if tp == 'id' then
				ar[ i ] = ar[ i ].id
			end
			if ar[ i ] ~= '' and f ~= '' then
				ar[ i ] = mw.ustring.format( 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( pr, f, c, v, l )
		if #arr == 0 then
			arr = getWikidataValues( pr, v or '', c or 1, f or '', l, entity, page, country )
		else
			subArr = getWikidataValues( pr, v or '', c or 1, f or '', l, 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.p, p.f, p.c, p.v, p.l )
	elseif tp == 'table' then
		for key, value in ipairs( 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

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 = fw.getValuesWithLanguages( 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 = lg.getProperty( page.lang, 'w' ) -- writing entity id
			for key, value in pairs( addresses ) do
				-- same writing entity id as page.lang
				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 _, 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

	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

	mb.getNamesFromWikidata( vp.ParMap, vp.wdContent, page, country, entity )
	getAddressesFromWikidata( page, country, entity )

	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

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

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

local function finalParameterCheck( show, page, country )
	-- 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
	-- 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.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 =
			mb.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
		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 )
	vp.ParMap.commonscat = vp.ParMap.commonscat:gsub( ' ', '_' )

	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( page, country, entity, withFullStop )
	local r = ''
	local name = vp.ParMap.givenName.name
	local t, s = mb.makeSisterIcons( vp.ParMap, page, country, entity )
	-- social media including value check
	local u = mb.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
	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
	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 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.symbol 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, page, 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( page, 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, page, 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, ignoreUnicode = 1 } )
			.. '</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 subtypes
local function makeFeatures( show, subtypes )
	local result = ''
	local s, u, v

	-- merging subtypeAdd (from Wikidata) to manually entered subtypes
	vp.wdContent.subtypeAdd =
		type( vp.ParMap.subtypeAdd ) == 'table' and #vp.ParMap.subtypeAdd > 0
	if vp.wdContent.subtypeAdd and not show.nowdsubtype then
		-- making translation table from Wikidata ids to feature types
		local qt = {}
		for key, typeObj in pairs( vs.f ) do
			if typeObj.w ~= '' then -- does a Wikidata equivalent exist?
				qt[ typeObj.w ] = key
			end
		end

		-- 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 ] or ''
			end
		end
	end

	if not next( subtypes ) then
		return result
	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 mb.convertForSort( vs.f[ a.t ].n ) < mb.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="..."
				u = subtype.t .. ',' .. tostring( i )
				if type( subtype.c ) == 'number' and subtype.c > 1 then
					u = u .. ',' .. subtype.c
				end
				table.insert( data, u )
			end
		end
	end
	vp.ParMap.subtype = table.concat( data, ';' )

	-- make text output
	if #subtypes > 0 and show.subtype then
		s = {};
		for _, subtype in ipairs( subtypes ) do
			u = vs.f[ subtype.t ]
			if u.g >= vs.first then
				if not u.n or u.n == '' then
					u.n = subtype.t 
				end
				if type( subtype.c ) == 'number' and subtype.c > 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( '%[([^%[%]]*)%]', '' )
				end
				if u.f and u.f ~= '' then
					v = mw.ustring.format( mi.texts.subtypeFormat, v, u.f, v )
				end
				table.insert( s, v )
			end
		end
		s = table.concat( s, ', ' )
		if s ~= '' then
			result = ' <span class="listing-subtype' .. addWdClass( 'subtypeAdd' )
				.. '">' .. mw.ustring.format( mi.texts.subtype, s ) .. '</span>'
		end
	end

	return result
end

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

	-- inline description
	if vp.ParMap.description ~= '' and show.inlineDescription 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

	-- adding features
	r = r .. makeFeatures( show, subtypes )

	-- adding Unesco symbol
	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( page, country, entity, body ~= '' )
	end

	-- adding description in block mode
	if vp.ParMap.description ~= '' and not show.inlineDescription 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 = vp.ParMap.lastedit
	outdated = false
	if t ~= '' and not t:match( mi.lasteditPattern ) then
		mb.addMaintenance( mi.maintenance.wrongDate )
		t = ''
	end
	if t ~='' then
		local success;
		success, t = pcall( formatDate, t, 'M Y' )
		if not success then
			mb.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
	r = r .. ' <span class="listing-metadata listing-metadata-items">'
		.. '<span class="listing-metadata-item listing-lastedit'
		.. ( outdated and ' listing-outdated' or '' ) .. '"'
		.. ( t == '' and ' style="display:none"' or '' ) .. '><span>'
		.. mw.ustring.format( mi.texts.lastedit,
			t == '' and mi.texts.lasteditNone or t )
		.. '</span></span></span>'

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

	return removeFullStops( r ), noContent
end

-- vCard main function
function vc.vCard( frame )
	mb.initMaintenance( 'VCard' )
	local page = pd.getPageData()

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

	vp.ParMap.wikiPage = ''  -- associated Wikivoyage page in current branch

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

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

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

	-- 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, subtypes, 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 = mb.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
		mb.addMaintenance( mi.maintenance.unknownLanguage )
	end

	local ns = page.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