Modulo:VCard

El Vikivojaĝo
Salti al navigilo Salti al serĉilo

Versiokontrolo[redakti]

Versionomo en Vikidatumoj: 2021-12-07 Ok!

Ekzempleroj[redakti]

* {{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|20px|Deutschland]] | image = Berlin Friedrichstraße Galeries Lafayette.jpg | email = info@hotel.de | comment = y | 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 = y | name = Egipta muzeo en 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 = Egipta muzeo en Kairo | facebook = y | twitter = y }} <​!-- Marker-Modus -->
* {{vCard | wikidata = Q1142142 | auto = y | name = Nordiska museet }} Noch weiterer Text.
* {{vCard | wikidata = Q1142142 | auto = y | price = n }}
* {{vCard | wikidata = Q257342 | auto = y }}
* {{vCard | wikidata = Q28934 | auto = y | description = Test Durchhangeln. }}
* {{vCard | wikidata = Q4872 | auto = y }}
* {{vCard | wikidata = Q10697 | auto = y }}
* {{vCard | wikidata = Q12508 | auto = y | description = Test Durchhangeln. }}
* {{vCard | wikidata = Q46033 | description = Test Flughafen. }}
* {{vCard | wikidata = Q46033 | show = symbol | description = Test Flughafen mit Ikone. }}
* {{vCard | wikidata = Q13218762 | auto = y | description = Anzeige englische Wikipedia. }}
* {{vCard | wikidata = Q56506795 | auto = y | description = Ausgabe Anschriften. }}
* {{vCard | wikidata = Q47429618 | address = | address-local = | directions = | phone = | show = nowdsubtype | description = Test eingeschränkte WD-Ausgabe. }}
* {{vCard | wikidata = Q637739 | auto = y | description = Rollstuhl aus WD. }}
* {{vCard | name = Landesmuseum für Vorgeschichte | type = museum | wikidata = Q1332407 | auto = y | description = Uhrzeiten, Kommentare. }}
* {{vCard | name = Museo Nacional de Antropología | type = museum | wikidata = Q2917041 | auto = y | description = Zusammenfassung Kommentare. }}
  • 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. Merkmale: WLAN, Bar, Schwimmbecken, 89 Zimmer. Geöffnet: 7/24. Check-in: ab 14 Uhr. Check-out: bis 12 Uhr. Akzeptierte Zahlungsarten: Visa, Master, AmEx, Maestro. (52° 30′ 52″ N 13° 23′ 23″ O)

Modulaj dependecoj[redakti]

VCard

Marker utilities
Coordinates
Coordinates/i18n
Marker utilities/Groups
Marker utilities/i18n
Marker utilities/Maki icons
Marker utilities/Types
UrlCheck
UrlCheck/i18n
Wikidata utilities
Marker utilities/i18n
VCard/Cards
VCard/i18n
VCard/Params
VCard/Qualifiers
VCard/Subtypes
CountryData
CountryData/Currencies
CountryData/Geography
Wikidata utilities
Hours
Hours/i18n
Wikidata utilities
Hours/i18n
Languages
LinkMail
Link utilities
Link utilities/i18n
LinkPhone
Link utilities
Link utilities/i18n
Yesno
LinkSkype
Link utilities
Link utilities/i18n
No globals
PageData
Wikidata utilities
Yesno

-- module variable and administration
local vc = {
	moduleInterface = {
		suite  = 'vCard',
		serial = '2021-12-07',
		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' )

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
	if v and v:find( 'poi', 1, true ) then
		mu.addMaintenance( mi.maintenance.showPoiUsed )
	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 == '' or label == id 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

local function removeStringDuplicates( ar )
	local hash = {}
	local result = {}

	for _, val in ipairs( ar ) do
		if not hash[ val ] then
			table.insert( result, val )
			hash[ val ] = 1
		end
	end
	return result
end

local function removeTableDuplicates( ar )
	local hash = {}
	local result = {}
	local hashVal

	for _, tab in ipairs( ar ) do
		hashVal = tab.value .. '#' .. tab.comment
		if not hash[ hashVal ] then
			table.insert( result, tab )
			hash[ hashVal ] = 1
		end
	end
	return result
end

local function mergeComments( ar )
	if #ar > 1 then
		for i = #ar, 2, -1 do
			for j = 1, i - 1, 1 do
				if ar[ i ].value == ar[ j ].value and ar[ i ].comment ~= ''
					and ar[ j ].comment ~= '' then
					ar[ j ].comment = ar[ j ].comment .. '; ' .. ar[ i ].comment
					table.remove( ar, i )
					break
				end
			end
		end
	end
end

local function convertTableWithComment( ar )
	for i = 1, #ar, 1 do
		if ar[ i ].comment == '' then
			ar[ i ] = ar[ i ].value
		else
			ar[ i ] = ar[ i ].value .. ' ('.. ar[ i ].comment .. ')'
		end
	end
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 ] = { value = a, comment = 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 ] = { value = ar[ i ].value, comment = u }
					else
						table.remove( ar, i )
					end
				else
					ar[ i ] = { value = ar[ i ].value, comment = 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 propDef.v == 'au' or propDef.v == 'vq' then
			if ar[ i ] and ar[ i ].value == '' then
				table.remove( ar, i )
			end
		else
			if ar[ i ] == '' then
				table.remove( ar, i )
			end
		end
	end

	-- cleanup
	if propDef.v == 'au' or propDef.v == 'vq' then
		ar = removeTableDuplicates( ar )
		mergeComments( ar )
		convertTableWithComment( ar )
	else
		ar = removeStringDuplicates( ar )
	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 )

	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 simplifyString( s )
	s = mw.ustring.lower( s )
	s = mw.ustring.gsub( s, '[“”„‟]', '"' )
	s = mw.ustring.gsub( s, "[‘’‚‛]", "'" )
	return mw.ustring.gsub( s, "[‐‑‒–—―]", "-" )
end

local function compareLocal( value1, key2 )
	if not mu.isSet( value1 ) or not mu.isSet( vp.ParMap[ key2 ] ) then
		return
	end
	local s1 = simplifyString( value1 )
	local s2 = simplifyString( 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' )

	vp.ParMap.alt = mu.removeStars( vp.ParMap.alt )
	vp.ParMap.nameExtra = mu.removeStars( vp.ParMap.nameExtra )

	-- 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 subtypes = {}
	local vs = require( 'Module:VCard/Subtypes' )
	vs.n = vs.n + 1
	mu.addTypesToSubtypes( vs.f, vs.n )
	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
    if next( subtypes ) == nil then
        return
    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.cards[ t ] then
				vp.ParMap.payment[ i ] = vr.cards[ 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 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' )
		-- add node to save the parent tag
		:node( mw.html.create( 'span' )
			:attr( 'class', 'listing-metadata-item listing-lastedit' )
			:addClass( outdated and 'listing-outdated' or nil )
			:addClass( t == '' and 'listing-item-dummy' or nil )
			:css( 'display', t == '' and 'none' or nil )
			: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

local function makeMaintenance( page )
	local ns = page.namespace
	local r = ''
	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

-- 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 = makeContacts( country )
	if address ~= '' and contacts ~= '' then
		address = address .. ' '
	end
	contacts = removeFullStops( address .. contacts )
	
	-- 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

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

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

return vc