Modul:VCard
Die Dokumentation für dieses Modul kann unter Modul:VCard/doc erstellt werden
-- module variable and administration local vc = { moduleInterface = { suite = 'vCard', serial = '2022-12-17', item = 58187507 } } -- module import -- require( 'strict' ) 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 er -- modules will be loaded later if needed local hi local hr local lg local lp = require( 'Module:LinkPhone' ) local wu = require( 'Module:Wikidata utilities' ) local function addWdClass( key ) return mu.addWdClass( vp.wdContent[ key ] ) 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, v in pairs( vi.p ) do value = args[ key ] 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 = 'y' end t = mu.yesno( value ) if t then if vp.ParMap.wikidata ~= '' then vp.ParMap[ key ] = t == 'y' 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 if mu.isSet( vp.ParMap.styles ) then vp.ParMap.styles = mi.nameStyles[ vp.ParMap.styles:lower() ] or vp.ParMap.styles else vp.ParMap.styles = nil end end local function initialParameterCheck( frame ) local country, entity, param, show, t, v, wrongQualifier local args = mu.checkArguments( frame:getParent().args, vi.p ) vp.ParMap.wikidata, entity, wrongQualifier = wu.getEntity( args.wikidata or '' ) if wrongQualifier then mu.addMaintenance( mi.maintenance.wrongQualifier ) elseif mu.isSet( vp.ParMap.wikidata ) then mu.addMaintenance( mi.maintenance.wikidata ) end if mu.isSet( vp.ParMap.wikidata ) then v = mu.yesno( args.auto or '' ) if v then vp.ParMap.auto = v == 'y' else vp.ParMap.auto = mi.options.defaultAuto end else vp.ParMap.auto = false end -- making phone number table t = {} for i, key in ipairs( vp.phones ) do mu.tableInsert( t, args[ key ] ) end -- getting country-specific technical parameters country = cm.getCountryData( entity, t, args.country ) if country.fromWD then mu.addMaintenance( mi.maintenance.countryFromWD ) end if country.unknownCountry then mu.addMaintenance( mi.maintenance.unknownCountry ) 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 = args.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 i, 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 if unit.mul then a = mu.formatNumber( string.format( '%.2f', -- 2 decimal places tonumber( value.amount ) * unit.mul ) ) end if not mi.noCurrencyConversion[ unit.iso ] then if not er then er = require( 'Module:Exchange rate' ) end unit = er.getWrapper( a, unit.iso, '', 2, cm.getCurrencyFormatter ) .. mi.maintenance.currencyTooltip else unit = tostring ( mw.html.create( 'span' ) :addClass( 'voy-currency' ) :addClass( 'voy-currency-' .. unit.iso:lower() ) :wikitext( cm.getCurrencyFormatter( unitId ) ) ) end 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 .. ' ' .. 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:match( '^Q%d+$' ) then for i, tab in ipairs( tables ) do if type( tab[ id ] ) == 'string' then label = tab[ id ] break end end if label == '' then return label elseif label == id then label = mu.getTypeLabel( id ) 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 calendarmodel, minAge, maxAge for i, 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 j, id in ipairs( statement[ property ] ) do if statement[ pType ] == 'monolingualtext' then id = id.text elseif statement[ pType ] == 'time' then id, calendarmodel = 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 i = 1, #tab do if tab[ i ] == 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 i, 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 i, 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, isMobilephone, 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' or propDef.v == 'iqp' then q = propDef.v == 'iq' and mi.propTable.quantity or mi.propTable.policyComments ar = wu.getValuesWithQualifiers( entity, propDef.p, propDef.q, q, { 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, isMobilephone = 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' or propDef.v == 'iqp' then if ar[ i ][ 'value-type' ] ~= 'wikibase-entityid' then table.remove( ar, i ) end if propDef.v == 'iqp' then ar[ i ].policyComment, isMobilephone = getComments( ar[ i ], mi.propTable.policyComments, page ) end -- strings with qualifiers (for contacts) elseif propDef.v == 'vq' then if ar[ i ][ 'value-type' ] ~= 'string' then table.remove( ar, i ) else u, isMobilephone = getComments( ar[ i ], mi.propTable.contactComments, page ) if mi.options.useMobile and propDef.t then if ( isMobilephone and propDef.t == 'mobile' ) or ( not isMobilephone 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.unknownPropertyLanguage = 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 i, lng in ipairs( mi.langs ) 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 i, lng in ipairs( mi.langs ) 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[ 1 ] or '', 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 mu.checkImage( vp.ParMap, 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' ) mu.removeStars( vp.ParMap, { 'alt', 'nameExtra' } ) -- analysing addressLocal vs address if vp.ParMap.addressLang and vp.ParMap.addressLang == country.lang then vp.ParMap.addressLocal = '' end if vp.ParMap.addressLocal ~= '' and vp.ParMap.address == '' then vp.ParMap.address = mu.languageSpan( vp.ParMap.addressLocal, mi.texts.hintAddress, page, country ) vp.ParMap.addressLocal = '' vp.wdContent.address = vp.wdContent.addressLocal 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, group, and color if not mu.isSet( vp.ParMap.type ) and mu.isSet( defaultType ) then vp.ParMap.type = defaultType end mu.checkTypeAndGroup( vp.ParMap ) mu.getColor( vp.ParMap ) if mi.options.useTypeCateg and vp.ParMap.typeTable then for i, aType in ipairs( vp.ParMap.typeTable ) do mu.addMaintenance( mw.ustring.format( mi.maintenance.type, aType ) ) end end mu.checkStatus( vp.ParMap ) 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.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, lang ) return tostring( mw.html.create( isBdi and 'bdi' or 'span' ) :addClass( class ) :attr( { title = title, lang = lang } ) :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 ) local name = vp.ParMap.givenName.name local icons = {} local onlyWikidata = false if mi.options.showSisters then onlyWikidata = mu.makeSisterIcons( icons, vp.ParMap, page, country, entity ) end mu.makeSocial( icons, vp.ParMap, vp.wdContent, name ) if #icons > 0 then return ( onlyWikidata and '' or ' ' ) .. table.concat( icons, '' ) else return '' 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 local function makeMarkerAndName( show, page, country, frame ) local r = '' local m, s, t if vp.ParMap.before ~= '' then r = r .. makeSpan( vp.ParMap.before .. ' ', 'listing-before' ) end -- adding status icons t = mu.makeStatusIcons( vp.ParMap ) if t ~= '' then r = r .. t .. ' ' end -- adding POI marker mu.setCopyMarker( vp.ParMap, show ) if show.poi or mu.isSet( vp.ParMap.copyMarker ) 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 ) .. ' ' end -- adding names, url, comment and airport codes mu.checkUrl( vp.ParMap ) 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 s = mw.uri.anchorEncode( vp.ParMap.givenName.name ):gsub( '&', '&' ) :gsub( ''', "'" ) r = r .. tostring( mw.html.create( 'bdi' ) :attr( 'id', 'vCard_' .. s ) :attr( 'class', 'p-name p-org fn org listing-name' .. addWdClass( 'name' ) ) :cssText( vp.ParMap.styles ) :wikitext( t ) ) if vp.ParMap.url ~= '' and vp.ParMap.givenName.pageTitle ~= '' then r = r .. ' ' .. mu.addLinkIcon( 'listing-url', vp.ParMap.url, mi.iconTitles.internet, 'internet' ) end local tab = {} if mi.options.showLocalData then if vp.ParMap.nameLocal ~= '' then table.insert( tab, mu.languageSpan( vp.ParMap.nameLocal, mi.texts.hintName, page, country, 'listing-name-local' .. addWdClass( 'nameLocal' ) ) ) end if vp.ParMap.nameLatin ~= '' then table.insert( tab, makeSpan( vp.ParMap.nameLatin, 'listing-name-latin', mi.texts.hintLatin, false, mu.isSet( country.lang ) and ( country.lang .. '-Latn' ) or nil ) ) 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' , 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 i, 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 i, 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.unknownPropertyLanguage = 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( ', ' .. mu.languageSpan( vp.ParMap.addressLocal, mi.texts.hintAddress, page, country, 'listing-address-local' .. addWdClass( 'addressLocal' ) ), 'listing-add-info' ) end t = '' if vp.ParMap.directions ~= '' then t = makeSpan( vp.ParMap.directions, 'listing-directions' .. addWdClass( 'directions' ) ) end local s = '' if mi.options.showLocalData and vp.ParMap.directionsLocal ~= '' then s = 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 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 -- checking subtypes local function checkSubtypes( subtypesTable ) if not mu.isSet( vp.ParMap.subtype ) then return {} end local function aliasToSubtype( alias ) if not vc.subtypeAliases then -- alias to subtype table vc.subtypeAliases = mu.getAliases( subtypesTable, 'alias' ) end return vc.subtypeAliases[ alias ] end local function subtypeExists( subtype ) return subtypesTable[ subtype ] and subtype or aliasToSubtype( subtype ) end local subtypes = {} local invalidSubtypes = {} local at, count, invalidCount, item for subtype, v in pairs( mu.split( vp.ParMap.subtype, true ) ) do invalidCount = false count = '' item = subtype -- split item from count at = item:find( ':', 1, true ) if at then count = tonumber( item:sub( at + 1, #item ) ) or '' item = mw.text.trim( item:sub( 1, at - 1 ) ) if count == '' then invalidCount = true -- ':' without count or not a number else count = math.floor( count ) if count < 2 then count = '' end end end item = subtypeExists( item ) or mu.typeExists( item ) if item then subtypes[ item ] = count end if invalidCount or not item then table.insert( invalidSubtypes, subtype ) end end if #invalidSubtypes > 0 then mu.addMaintenance( mw.ustring.format( mi.maintenance.unknownSubtype, table.concat( invalidSubtypes, ', ' ) ) ) end return subtypes end -- making subtypes string local function makeFeatures( tab, show ) 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 ) or #vp.ParMap.subtypeTable > 0 ) then return end local vs = require( 'Module:VCard/Subtypes' ) local function getSubtypeParams( subtype ) local r = vs.f[ subtype ] or mu.getTypeParams( subtype ) if not r.n then r.n = r.label or subtype end r.g = r.g or vs.n return r end local subtypes = checkSubtypes( vs.f ) -- merging subtypeAdd (from Wikidata) to manually entered subtypes local unknowWDfeatures = false local count, p, t if vp.wdContent.subtypeAdd then -- making translation table from Wikidata ids to feature types local subtypeIds = mu.getAliases( vs.f, 'wd' ) -- adding type if Wikidata id (wd.value) is known -- indexed array prevents multiple identical types for i, wd in ipairs( vp.ParMap.subtypeAdd ) do t = subtypeIds[ wd.value ] or mu.idToType( wd.value ) if t then subtypes[ t ] = { c = ( wd[ mi.properties.quantity ] and wd[ mi.properties.quantity ][ 1 ] ) or ( wd[ mi.properties.capacity ] and wd[ mi.properties.capacity ][ 1 ] ) or '', p = wd.policyComment } 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 and #vp.ParMap.subtypeTable == 0 then return end -- replace selected subtypes for subtype, count in pairs( subtypes ) do if vs.convert[ subtype ] then if type( count ) == 'table' then p = count.p count = count.c end t = vs.convert[ subtype ][ count ] or vs.convert[ subtype ][ 1 ] subtypes[ t ] = { p = p } subtypes[ subtype ] = nil end end -- make subtypes table sortable local s = {}; for subtype, count in pairs( subtypes ) do if type( count ) == 'table' then table.insert( s, { t = subtype, c = count.c, p = count.p } ) else table.insert( s, { t = subtype, c = count } ) end end -- add subtypes from types table if vp.ParMap.subtypeTable then for i, subtype in ipairs( vp.ParMap.subtypeTable ) do table.insert( s, { t = subtype, c = 1 } ) end end -- sorting subtypes -- by subtype group and then alphabetically by name table.sort( s, function( a, b ) local at = getSubtypeParams( a.t ) local bt = getSubtypeParams( b.t ) local na = mu.convertForSort( at.n ) local nb = mu.convertForSort( bt.n ) return ( at.g < bt.g ) or ( at.g == bt.g and na < nb ) end ) -- make text and data output local data = {} -- for data-subtype attribute in wrapper tag if #s > 0 then local r = {}; local subtype, f, u, u_n, v for i = 1, #s do subtype = s[ i ] -- for data-subtype="..." in wrapper tag u = subtype.t .. ',' .. tostring( i ) if type( subtype.c ) == 'number' and subtype.c > 1 then u = u .. ',' .. subtype.c end table.insert( data, u ) u = getSubtypeParams( subtype.t ) if u.g >= vs.first then 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( '%[([^%[%]]*)%]', '' ) end if mu.isSet( u.t ) then -- string tooltip v = mw.ustring.format( mi.texts.subtypeSpan, u.t, v ) elseif mu.isSet( u.f ) then -- icons f = mw.ustring.format( mi.texts.subtypeFile, u.f, v ) if u.c then f = mw.ustring.rep( f, u.c ) end v = mw.ustring.format( mi.texts.subtypeAbbr, v, f ) end -- adding policy comment if subtype.p and subtype.p ~= '' then v = v .. ' (' .. subtype.p .. ')' end end table.insert( r, v ) end if #r > 0 then r = #r == 1 and mw.ustring.format( mi.texts.subtype, r[ 1 ] ) or mw.ustring.format( mi.texts.subtypes, table.concat( r, ', ' ) ) if r ~= '' then table.insert( tab, makeSpan( r, 'listing-subtype' .. addWdClass( 'subtypeAdd' ) ) ) end end end -- subtype contains now the value for wrapper tag vp.ParMap.subtype = table.concat( data, ';' ) 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 class = class .. mu.addWdClass( #vp.ParMap.payment > 0 ) 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 ) :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 ~= '' and mi.options.showUnesco then local uLink, uTitle = require( 'Module:VCard/Unesco' ).getUnescoInfo( country ) table.insert( tab, mu.addLinkIcon( 'listing-unesco voy-symbol-unesco', uLink, uTitle, 'unesco' ) ) 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 ) ) 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 = mu.getPageData() -- getting location (vCard/listing) entity, show options and country data local defaultType = frame.args.type local vcEntity, show, country = initialParameterCheck( frame ) -- 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 ) contacts = removeFullStops( address .. contacts ) -- making description, coordinates, and meta data local description, noContent = makeDescription( show, page, country, vcEntity ) local r = leading local icons = '' if contacts == '' and noContent then show.inline = true r = r .. makeIcons( page, country, vcEntity ) .. description else if type( mi.options.showSisters ) == 'boolean' then -- could also be 'atEnd', then part of body icons = makeIcons( page, country, vcEntity ) end r = removeFullStops( r .. contacts .. icons .. '. ' .. description ) :gsub( '%)%s+%(', '; ' ) end -- error handling and maintenance, not in template and module namespaces if country.unknownLanguage then mu.addMaintenance( mi.maintenance.unknownLanguage ) end if country.unknownPropertyLanguage then mu.addMaintenance( mi.maintenance.unknownPropertyLanguage ) end r = r .. makeMaintenance( page ) -- wrapping tag vp.ParMap.address = vp.ParMap.addressOrig return mu.makeWrapper( r, vp.ParMap, page, country, show, vp.vcardData, 'vCard', frame ) end return vc