Modul:Nabostasjoner
Hopp til navigering
Hopp til søk
Dokumentasjon for denne modulen kan opprettes på Modul:Nabostasjoner/dok
local p = {} --Lua code made by Eru aimed at presenting preceding and following subway stations, sorted by geographical order and by service/line number/name. --For time being, still in bêta. Original code at https://commons.wikimedia.org/wiki/Module:Adjacent_stations --If any requests or comments, please add them to (where?) --Do not copy for time being -- default params local defaultLang = mw.language.getContentLanguage().code local defaultLink = 'nowiki' -- modify to your local wiki, like frwiki local size_logo = 12 local showdebug = false local ignoreCoordinate = false local linkback local showsource = false--parameter to help present sources, if they are present in Wikidata local with_bearing=true--parameter to help decide to calculate with bearing or with classic line direction method (false) -- module loading local function loadModule(name, name2) local exist, res = pcall(require, name) if not exist then exist, res = pcall(require, name2) end return exist and res end local wikidata if defaultLink == 'eowiki' or defaultLink == 'wikidata' or defaultLink == 'ptwiki' or defaultLink == 'fiwiki' or defaultLink == 'nowiki' then -- too much incompatibilities in [[Module:Wikidata]] wikidata = loadModule( 'Modul:Wikidata2' ) -- original code at https://fr.wikipedia.org/wiki/Module:Wikidata -- wikidata.getReferences = nil -- wikidata.sourceStr = nil else wikidata = loadModule( 'Module:Wikidata' ) -- original code at https://fr.wikipedia.org/wiki/Module:Wikidata or https://commons.wikimedia.org/wiki/Module:Wikidata end local tools = loadModule( 'Modul:Wikidata/Tools' ) -- original code at https://commons.wikimedia.org/wiki/Module:Wikidata/Tools local TNT = require 'Modul:TNT' -- https://www.mediawiki.org/wiki/Module:TNT -- compatiblity of [[Module:Wikidata]] on various wiki wikidata.formatEntity = wikidata.formatEntity -- frwiki or wikidata._getLabel -- commons wikidata.getId = wikidata.getId -- frwiki or ( tools and tools.getId ) -- commons wikidata.getFormattedQualifiers = wikidata.getFormattedQualifiers -- frwiki or wikidata.formatStatementQualifiers -- commons wikidata.sourceStr = wikidata.sourceStr -- frwiki or function( sources, hashes ) -- commons return sources end wikidata.stringTable = wikidata.stringTable -- commons or wikidatafr.stringTable -- eowiki wikidata.getClaims = wikidata.getClaims -- commons or wikidatafr.getClaims -- eowiki -- private functions local function getMessage(key, ...) return TNT.formatInLanguage(defaultLang, 'I18n/Template:Stations-voisines.tab', key, {...}) -- loading of https://commons.wikimedia.org/wiki/Data:I18n/Template:Stations-voisines.tab end --from https://commons.wikimedia.org/wiki/Module:Bearing require ('Module:No globals'); local function bearing (from_lat, from_long, to_lat, to_long) if not (to_lat and to_long) then return nil end local from_lat_rad = math.rad (from_lat); -- convert degree inputs to radians local from_long_rad = math.rad (from_long); local to_lat_rad = math.rad (to_lat); local to_long_rad = math.rad (to_long); local delta_long_rad = to_long_rad - from_long_rad; local x = math.cos (to_lat_rad) * math.sin (delta_long_rad); local y = (math.cos (from_lat_rad) * math.sin (to_lat_rad)) - (math.sin (from_lat_rad) * math.cos (to_lat_rad) * math.cos (delta_long_rad)); local beta = math.atan2 (x, y); -- result in radians beta = math.deg (beta); -- convert to degrees return (0.0 > beta) and (beta + 360.0) or beta; -- when beta is negative, add 360 degrees to make a positive bearing end --[[--------------------------< E X P O R T S >---------------------------------------------------------------- ]] -- from [[fr:Module:Outils]] local function trim( texte ) if type( texte ) == 'string' and texte ~= '' then texte = texte:gsub( '^%s*(%S?.-)%s*$', '%1' ) if texte ~= '' then return texte end end return nil end local function validTextArg( args, name, ... ) local texte = trim( args[name] ) if texte then return texte end if select( '#', ... ) > 0 then return validTextArg( args, ... ) end return nil end local function extractArgs ( frame ) if type( frame.getParent ) == 'function' then local args = frame:getParent().args for k,v in pairs( frame.args ) do args[k] = v; end return args else return frame end end -- from [[fr:Module:Wikidata]] local function getMainId(claim) return wikidata.getId(claim.mainsnak) end local function entityId(entity) if type(entity) == 'string' then return entity elseif type(entity) == 'table' then return entity.id end end local function addLinkBack(str, id, property) if not id or id == '' then id = wikidata.getEntityIdForCurrentPage() end if not id then return str end if type(property) == 'table' then property = property[1] end id = entityId(id) local class = '' if property then class = 'wd_' .. string.lower(property) end local icon = '[[File:Blue pencil.svg|%s|10px|baseline|class=noviewer|link=%s]]'--pencil icon to help modify Wikidata local title = getMessage('see-wikidata-value') local url = mw.uri.fullUrl('d:' .. id, 'uselang=fr') url.fragment = property -- ajoute une #ancre si paramètre "property" défini url = tostring(url) local v = mw.html.create('span') :addClass(class) :wikitext(str) :tag('span') :addClass('noprint wikidata-linkback') :wikitext(icon:format(title, url)) :allDone() return tostring(v) end -- from [[commons:Module:Wikidata]] local function getQualifiers(statement, qualifs, params) if not statement.qualifiers then return nil end local vals = {} for i, j in pairs(qualifs) do j = string.upper(j) if statement.qualifiers[j] then local inserted = false if statement.qualifiers[j][1].datatype == 'monolingualtext' then local in_preferred_lang for _, language in ipairs(fb.fblist(params.lang or defaultlang, true)) do for _, snak in ipairs(statement.qualifiers[j]) do if isInLanguage(snak, language) then in_preferred_lang = snak break end end if in_preferred_lang then break end end if in_preferred_lang then table.insert(vals, in_preferred_lang) inserted = true end end if not inserted then for _, snak in pairs(statement.qualifiers[j]) do table.insert(vals, snak) end end end end if #vals == 0 then return nil end return vals end wikidata.getReferences = wikidata.getReferences or function(statement) local cite = loadModule( 'Module:Cite' ) local isSpecial = wikidata.isSpecial or ( tools and tools.isSpecial ) if not isSpecial or not cite or not statement.references then return '' end local frame = mw.getCurrentFrame() local sourcestring = '' for i, ref in pairs(statement.references) do local s if ref.snaks.P248 then for j, source in pairs(ref.snaks.P248) do if not isSpecial(source) then local page if ref.snaks.P304 and not isSpecial(ref.snaks.P304[1]) then page = ref.snaks.P304[1].datavalue.value end s = cite.citeitem('Q' .. source.datavalue.value['numeric-id'], lang, page) s = frame:extensionTag('ref', s) sourcestring = sourcestring .. s end end elseif ref.snaks.P854 and not isSpecial(ref.snaks.P854[1]) then s = frame:extensionTag('ref', wikidata.formatSnak(ref.snaks.P854[1], {})) sourcestring = sourcestring .. s end end return sourcestring end -- public functions function p.Adjacent_stations( id, onlyLineService, rows ) if not id then id = mw.wikibase.getEntityIdForCurrentPage() end local argsData = { entity = id, property = 'P197' } if onlyLineService then argsData.qualifier = { 'P1192', 'P81' } -- ligne or service argsData.qualifiervalue = onlyLineService end --localisation principale local mainCoordinate = ( not ignoreCoordinate ) and wikidata.stringTable( { entity = id, property = 'P625', numval = 1 } ) if mainCoordinate and #mainCoordinate > 0 then -- si des coordonnées pour la présente station sont présentes / if coordinates are present for the current subway mainCoordinate = mainCoordinate[ 1 ] end -- ajout d'une ligne dans le tableau / add a line in the table local function addLineDetail( lineDetail ) if lineDetail.color then lineDetail.color = 'style="background:#' .. lineDetail.color .. ';" width="1*" |' else lineDetail.color = '' end local textBefore = { '| ' } local textAfter = { '| ' } local textLine = { '| ' } if lineDetail.logo then for _, logo in ipairs( lineDetail.logo ) do textLine[ #textLine + 1 ] = '[[Image:' .. logo .. '|' .. size_logo .. ' px]] ' end end textLine[ #textLine + 1 ] = lineDetail.name if lineDetail.source then textLine[ #textLine + 1 ] = lineDetail.source end local isTerminus = false if lineDetail.after and lineDetail.after ~= '-' then -- no value textAfter[ #textAfter + 1 ] = lineDetail.after if lineDetail.afterDestination and lineDetail.afterDestination ~= '-' then textAfter[ #textAfter + 1 ] = "<br /><small>" if lineDetail.after == lineDetail.afterDestination then textAfter[ #textAfter + 1 ] = getMessage( 'if_terminus' ) else textAfter[ #textAfter + 1 ] = getMessage( 'towards' ) textAfter[ #textAfter + 1 ] = " ''" textAfter[ #textAfter + 1 ] = lineDetail.afterDestination textAfter[ #textAfter + 1 ] = "''" end textAfter[ #textAfter + 1 ] = "</small>" end if lineDetail.afterVia and lineDetail.afterVia ~= '-' then textAfter[ #textAfter + 1 ] = "<br /><small>" textAfter[ #textAfter + 1 ] = getMessage( 'via' ) textAfter[ #textAfter + 1 ] = " ''" textAfter[ #textAfter + 1 ] = lineDetail.afterVia textAfter[ #textAfter + 1 ] = "''</small>" end lineDetail.colorAfter = lineDetail.color elseif lineDetail.Y then lineDetail.colorAfter = '' else isTerminus = true textLine[ #textLine + 1 ] = "<br /><small>" textLine[ #textLine + 1 ] = getMessage( 'terminus' ) textLine[ #textLine + 1 ] = "</small>" lineDetail.colorAfter = '' end if lineDetail.before and lineDetail.before ~= '-' then -- no value textBefore[ #textBefore + 1 ] = lineDetail.before if lineDetail.beforeDestination and lineDetail.beforeDestination ~= '-' then textBefore[ #textBefore + 1 ] = "<br /><small>" if lineDetail.before == lineDetail.beforeDestination then textBefore[ #textBefore + 1 ] = getMessage( 'if_terminus' ) else textBefore[ #textBefore + 1 ] = getMessage( 'towards' ) textBefore[ #textBefore + 1 ] = " ''" textBefore[ #textBefore + 1 ] = lineDetail.beforeDestination textBefore[ #textBefore + 1 ] = "''" end textBefore[ #textBefore + 1 ] = "</small>" end if lineDetail.beforeVia and lineDetail.beforeVia ~= '-' then textBefore[ #textBefore + 1 ] = "<br /><small>" textBefore[ #textBefore + 1 ] = getMessage( 'via' ) textBefore[ #textBefore + 1 ] = " ''" textBefore[ #textBefore + 1 ] = lineDetail.beforeVia textBefore[ #textBefore + 1 ] = "''</small>" end lineDetail.colorBefore = lineDetail.color else isTerminus = true textLine[ #textLine + 1 ] = "<br /><small>" textLine[ #textLine + 1 ] = getMessage( 'terminus' ) textLine[ #textLine + 1 ] = "</small>" lineDetail.colorBefore = '' end local inverted --new decision tree with bearings to help decide how to build the table, provided both neighbours have coordinates, disable it with 'with_bearing' if with_bearing and (lineDetail.beforeCoordinate or lineDetail.afterCoordinate) then local bearingbefore local bearingafter local angle if lineDetail.beforeCoordinate then bearingbefore=bearing ( mainCoordinate.latitude, mainCoordinate.longitude, lineDetail.beforeCoordinate.latitude, lineDetail.beforeCoordinate.longitude) end if lineDetail.afterCoordinate then bearingafter=bearing ( mainCoordinate.latitude, mainCoordinate.longitude, lineDetail.afterCoordinate.latitude, lineDetail.afterCoordinate.longitude) if bearingbefore and bearingafter then angle = 180 - math.abs(math.abs(bearingbefore - bearingafter) - 180) end end if bearingafter==nil and bearingbefore==nil then -- bug elseif bearingafter==nil then--termini case, only with bearingbefore --if bearingbefore<=205 --not too close to 180°if (bearingafter<=45.0 or bearingafter>=225.0) --lazily heading North if(bearingbefore>45.0 and bearingbefore<225.0) --lazily heading South/East for "before", modified then inverted=true else inverted=false end elseif bearingbefore==nil then -- bug elseif (bearingbefore<45.0 or bearingbefore>315.0) --truely heading North for "before" for 90° then if (bearingafter>=45.0 and bearingafter<=225.0) --lazily heading South or East for "after" for 180° then if angle>45.0 then inverted=false --their angle should be more than 45° so it is more of simple direction else inverted=true end --then ↕ elseif (bearingbefore<45.0 and bearingafter>225.0) --before in North and after in East/South then if angle>45.0 then inverted=true --their angle should be more than 45° so it is more of simple direction else inverted=false end elseif ( bearingbefore>315.0 and bearingafter<45.0) --before in North and after in North too? bizarre then inverted=true else inverted=false -- end elseif (bearingbefore>135.0 and bearingbefore<225.0) --truely heading South for "before" for 90° then if (bearingafter<=45.0 or bearingafter>=225.0) --lazily heading North or East for "after" for 180° then if angle>45.0 then inverted=true --then ↕ else inverted=false end elseif (bearingbefore>=180.0 and bearingafter<135.0) --probably ┌ then inverted=false elseif ( bearingbefore<=180.0 and bearingafter<225.0) --probably ┐ , hard to tell then inverted=false else inverted=true end elseif (bearingbefore>45.0 and bearingbefore<135.0) --truely heading West for "before" for 90° then if (bearingafter>=225.0 or bearingafter<=45.0)--lazily heading East or North for "after" for 180° then if angle>45.0 then inverted=true --then ↕ else inverted=false end --then ↔ else inverted=true --probably ┌ or ┐ end elseif (bearingbefore>225.0 and bearingbefore<315.0) --truely heading East for "before" for 90° then if (bearingafter>=45.0 and bearingafter<=225.0) --lazily heading West or North for "after" for 180° then if angle>45.0 then inverted=false --then ↕ else inverted=true end --then ↔ else inverted=false --probably ┌ or ┐ end elseif (bearingbefore<135.0) --lazily heading NorthWest for "before" or (bearingafter>135.0) --lazily heading SouthEast for "after" then inverted=false elseif (bearingbefore>=135.0) --lazily heading SouthEast for "before" or (bearingafter<=135.0) --lazily heading NorthWest for "after" then inverted=true elseif bearingbefore>bearingafter then inverted=true --cas des bearings presques identiques --else inverted=true --what about those having almost same bearing? end --decision tree without bearing consideration elseif lineDetail.beforeCoordinate and lineDetail.afterCoordinate then local direction = lineDetail.beforeDirection or lineDetail.afterDirection if direction == 'Q679' -- west or direction == 'Q684' then -- est if lineDetail.beforeCoordinate.longitude and lineDetail.afterCoordinate.longitude and lineDetail.beforeCoordinate.longitude > lineDetail.afterCoordinate.longitude then inverted = true end else -- north or south if lineDetail.beforeCoordinate.latitude and lineDetail.afterCoordinate.latitude and lineDetail.beforeCoordinate.latitude < lineDetail.afterCoordinate.latitude then inverted = true end end elseif isTerminus and lineDetail.beforeCoordinate and not lineDetail.after and mainCoordinate then local direction = lineDetail.beforeDirection if direction == 'Q679' -- west or direction == 'Q684' then -- est if lineDetail.beforeCoordinate.longitude and mainCoordinate.longitude and lineDetail.beforeCoordinate.longitude > mainCoordinate.longitude then inverted = true end else -- north or south if lineDetail.beforeCoordinate.latitude and mainCoordinate.latitude and lineDetail.beforeCoordinate.latitude < mainCoordinate.latitude then inverted = true end end end rows[ #rows + 1 ] = '|-' if inverted then if showdebug then textLine[ #textLine + 1 ] = '<br>' if lineDetail.afterDirection then textLine[ #textLine + 1 ] = lineDetail.afterDirection end textLine[ #textLine + 1 ] = 'ß of after' -- if bearingafter then textLine[ #textLine + 1 ] = bearingafter end textLine[ #textLine + 1 ] = ' <=> ' if lineDetail.beforeDirection then textLine[ #textLine + 1 ] = lineDetail.beforeDirection end textLine[ #textLine + 1 ] = 'ß of before' -- if bearingbefore then textLine[ #textLine + 1 ] = bearingbefore end end rows[ #rows + 1 ] = table.concat( textAfter ) rows[ #rows + 1 ] = '| ' .. lineDetail.colorAfter rows[ #rows + 1 ] = table.concat( textLine ) rows[ #rows + 1 ] = '| ' .. lineDetail.colorBefore rows[ #rows + 1 ] = table.concat( textBefore ) else if showdebug then textLine[ #textLine + 1 ] = '<br>' if lineDetail.beforeDirection then textLine[ #textLine + 1 ] = lineDetail.beforeDirection end textLine[ #textLine + 1 ] = 'ß before' -- if bearingbefore and bearingafter then textLine[ #textLine + 1 ] = bearingbefore end textLine[ #textLine + 1 ] = ' ― ' if lineDetail.afterDirection then textLine[ #textLine + 1 ] = lineDetail.afterDirection end textLine[ #textLine + 1 ] = 'ß after' -- if bearingbefore and bearingafter then textLine[ #textLine + 1 ] = bearingafter end end rows[ #rows + 1 ] = table.concat( textBefore ) rows[ #rows + 1 ] = '| ' .. lineDetail.colorBefore rows[ #rows + 1 ] = table.concat( textLine ) rows[ #rows + 1 ] = '| ' .. lineDetail.colorAfter rows[ #rows + 1 ] = table.concat( textAfter ) end end local claims = wikidata.getClaims( argsData ) local function getClaimDetail( claim ) claim.lineid = wikidata.getId( claim.currentline ) claim.lineName = wikidata.formatSnak( claim.currentline, { link = defaultLink } ) if not claim.lineName or claim.lineName == '-' then -- no value claim.lineName = '' end local routenumber = wikidata.formatStatements( { entity = claim.lineid, property = 'P1671', numval = 1 } ) if routenumber then claim.sortKey = tonumber( routenumber ) -- 570000 or tonumber( tostring( routenumber:gsub( ' ', '' ) ) ) -- 655 000 or routenumber:upper() -- KBS 566 else claim.sortKey = claim.lineName:upper() end end if claims and #claims > 0 then -- récupération des données de bases et de tri des lignes / recovery of basic data and line sorting local newClaims = { } local defaultSortKey = 1 for _, claim in ipairs( claims ) do claim.defaultSortKey = defaultSortKey defaultSortKey = defaultSortKey + 1 local currentlines = getQualifiers( claim, { 'P1192', 'P81' }, { } ) -- ligne or service if currentlines and #currentlines > 0 then for _,currentline in ipairs( currentlines ) do local newClaim = mw.clone( claim ) newClaim.currentline = currentline getClaimDetail( newClaim ) if not onlyLineService or onlyLineService == newClaim.lineid then newClaims[ #newClaims + 1 ] = newClaim end end else claim.lineName = '' claim.sortKey = claim.lineName:upper() newClaims[ #newClaims + 1 ] = claim end end table.sort( newClaims, function( c1, c2 ) if c1.sortKey == c2.sortKey then return c1.defaultSortKey < c2.defaultSortKey elseif type( c1.sortKey ) == 'number' and type( c2.sortKey ) == 'number' then return c1.sortKey < c2.sortKey else return tostring( c1.sortKey ) < tostring( c2.sortKey ) end end ) -- parcours de toutes les gares et stations / loop of all stations local lineDetail = { } for _, claim in ipairs( newClaims ) do -- nouvelle ligne if claim.lineName ~= lineDetail.name then if lineDetail.name ~= nil then addLineDetail( lineDetail ) end lineDetail = { } lineDetail.name = claim.lineName if claim.currentline then lineDetail.color = wikidata.formatStatements( { entity = claim.lineid, property = 'P465', numval = 1} ) lineDetail.logo = wikidata.stringTable( { entity = claim.lineid, property = 'P154' } ) end if showsource then lineDetail.source = wikidata.sourceStr( wikidata.getReferences(claim) ) end end local station = wikidata.formatStatement( claim, { link = defaultLink } ) local destination = wikidata.getFormattedQualifiers( claim, { 'P5051' }, { conjtype = getMessage( 'or', ' ' ), link = defaultLink } ) local via = wikidata.getFormattedQualifiers( claim, { 'P2825' }, { conjtype = getMessage( 'and' ), link = defaultLink } ) local coordinate = ( not ignoreCoordinate ) and wikidata.stringTable( { entity = getMainId( claim ), property = 'P625', numval = 1 } ) local terminusDirection if claim.lineid and coordinate and #coordinate > 0 then -- si des coordonnées sont présentes / if coordinates are present coordinate = coordinate[ 1 ] local terminusDirections = wikidata.getClaims( { entity = claim.lineid, property = 'P559', showqualifier = 'P560' } ) -- récupérer la liste des terminus et des directions de la ligne if terminusDirections and #terminusDirections > 0 then local terminus = getQualifiers( claim, { 'P5051' } ) -- récupérer les terminus de la connexion en cours / retrieve the terminus of the current connection local terminusQid = { } if terminus then for i, t in ipairs( terminus ) do local tid = wikidata.getId( t ) if tid then terminusQid[ tid ] = true end end end for _, td in ipairs( terminusDirections ) do local d = wikidata.getFormattedQualifiers( td, { 'P560' }, { displayformat = 'raw' } ) local t = getMainId( td ) if not terminusDirection and d and terminusQid[ t ] then -- si ce terminus de la ligne fait partie des terminus de ma connexion, prendre cette direction terminusDirection = d -- plusieurs résultats possibles, prendre le premier / several possible result, take the first end end end end if not lineDetail.before then -- station 1 lineDetail.before = station lineDetail.beforeDestination = destination lineDetail.beforeVia = via lineDetail.beforeCoordinate = coordinate -- lineDetail.beforeBearing=bearingbefore does not work, why? lineDetail.beforeDirection = terminusDirection elseif not lineDetail.after then -- station 2 lineDetail.after = station lineDetail.afterDestination = destination lineDetail.afterVia = via lineDetail.afterCoordinate = coordinate -- lineDetail.afterBearing=bearingafter does not work, why? lineDetail.afterDirection = terminusDirection else -- station 3 > nouvelle ligne dans le tableau local newLineDetail = { } newLineDetail.name = lineDetail.name newLineDetail.color = lineDetail.color newLineDetail.logo = lineDetail.logo newLineDetail.before = station newLineDetail.beforeDestination = destination newLineDetail.beforeVia = via newLineDetail.beforeCoordinate = coordinate newLineDetail.beforeDirection = terminusDirection newLineDetail.Y = true addLineDetail( lineDetail ) lineDetail = newLineDetail end end addLineDetail( lineDetail ) -- ajout de la dernière ligne construite / addition of the last line constructed local lb = '' if linkback ~= '-' then lb = '[[d:' .. id .. '#P197|' .. getMessage( 'edit_wikidata' ) .. ']]' rows[ #rows + 1 ] = '|-' rows[ #rows + 1 ] = '| class="noprint navigation-not-searchable tfoot" colspan="5" | ' .. lb end return true else return false end end function p.main( frame ) local rows = {} local args = extractArgs( frame ) local id = validTextArg( args, 1, 'id', 'wikidata', 'entity' ) local onlyLineService = validTextArg( args, 2, 'ligne', 'line', 'service' ) linkback = validTextArg( args, 'linkback' ) local ssource = validTextArg( args, 'showsource' ) if ssource and ( ssource == '-' or ssource == 'non' or ssource == 'no' or ssource == 'false' ) then showsource = false end local title = validTextArg( args, 'titre', 'title', 3 ) or '' if id and title == '' then title = wikidata.formatEntity( id , { link = defaultLink } ) if linkback ~= '-' and defaultLink ~= 'wikidata' then title = addLinkBack( title, id, 'P197' ) linkback = '-' end elseif title == '-' then title = '' end ignoreCoordinate = validTextArg( args, 'ignore coordinate', 'ignore coordonnées' ) showdebug = validTextArg( args, 'debug' ) -- pour les tests uniquement / for testing only rows[ #rows + 1 ] = '{| class="wikitable adjacent-stations"' rows[ #rows + 1 ] = '! scope=col | ' .. getMessage( 'station_before', '<abbr title="', '">', '</abbr> <abbr title="', '">', '</abbr> ', ' <abbr title="', '">', '</abbr> ') rows[ #rows + 1 ] = '! scope=col colspan="3" | ' .. title rows[ #rows + 1 ] = '! scope=col | ' .. getMessage( 'station_after', '<abbr title="', '">', '</abbr> <abbr title="', '">', '</abbr> ', ' <abbr title="', '">', '</abbr> ' ) local found = p.Adjacent_stations( id, onlyLineService, rows ) if found then rows[ #rows + 1 ] = '|}' return table.concat( rows, '\n' ) else return nil end end return p