Vai al contenuto

Modulo:GetNumber

Da Wikivoyage.

La documentazione per questo modulo può essere creata in Modulo:GetNumber/man

--[=[ GetNumber 2023-12-31
* local nilIf (nil handling function for internal use)
* local coalesce (nil handling function for internal use)
* local _round (rounding function for internal use)
*
* local getNumbersAsTable (internal use)
*
* getNumbersWithUnit
* getNumbersWithDate
]=]
local GetNumber = {}

-- it: format 1234.56 this way: 1 234,56 (as per international syntax)
-- de: format 1234.56 this way: 1.234,56 (as per Italian & German syntax)
local lang = mw.language.new( 'de' ) 

-- units and conversion
-- getting conversion data from wikidata is not implemented yet
local units = require ( 'Module:GetNumber/Units' )

-- returns nil, if both values are equal, otherwise the value
-- similar to the SQL function nullif()
local function nilIf ( value, equalValue )

   if ( value == nil ) then
      return nil
   elseif ( tostring ( value ) == tostring ( equalValue ) ) then
      return nil
   else
      return value
   end

end

-- returns the first value that is not nil
-- similar to the SQL function coalesce()
local function coalesce ( value1, value2, value3 )
   return value1 or value2 or value3
end

-- round function, which is not available in Lua
local _round = function  ( value, precision )
   local rescale = math.pow(10, precision or 0);
   return math.floor(value * rescale + 0.5) / rescale;
end

-- getNumbersAsTable() -> just for internal use.
-- gets the amount of a property including its unit and date of validity
--   id: Wikidata-ID (own, if not provided)
--   property: requested property
--   unit: wanted unit (standard unit, if not provided)
--         if the wanted unit is not provided, a conversion will be tried based on the given conversion numbers
--   values:
--      - single (standard): Only one (first) entry is shown
--      - all: All entries are fetched

-- the functions returns a table with the folowing columns:
-- amount
-- unit
-- unitOriginal (differs to unit, if the requested unit is converted)
-- year (set to 0, if not provided)
local getNumbersAsTable = function ( id, property, unit, values )

   -- local variables
   -- ID of the item
   -- Determined, if not provided
   local localID = id or mw.wikibase.getEntityIdForCurrentPage() or ''
   
   -- compatibility to existing modules and templates:
   -- some use the keyword "self" for using the own entity-ID
   if coalesce ( nilIf ( localID, 'self' ), '' ) == '' then
      localID = mw.wikibase.getEntityIdForCurrentPage() or ''
   end

   -- no Wikidata object
   if localID == '' then
      return {}, '', ''
   end

   -- property
   local requestedProperty = property or 'none';
   if string.sub(requestedProperty,1,1) ~= 'P' then
      requestedProperty = 'none'
   end

   -- no property given: exit
   if requestedProperty == 'none' then
      return {}, localID
   end

   -- property unknown in table "units": exit
   if units[requestedProperty] == nil then
      return {}, localID
   end

   -- unit
   local requestedUnit = 'none'
   if coalesce ( unit, '' ) == '' then
      if units[requestedProperty] ~= nil then
         requestedUnit = units[requestedProperty].standard
      end
   else
      requestedUnit = unit
      if units[requestedUnit] == nil then
      end
   end

   -- values
   local requestedValues = values or 'single'

   -- control variable
   -- is set to true, when requested unit is got on the first run
   local hasRequested = false

   -- getting the values
   local wdStatements = mw.wikibase.getBestStatements( localID, requestedProperty )

   -- running through the array and store it in a simple table
   local wdValues = {}
   local referenceValues = {}
   local entryValue
   for i, entry in ipairs ( wdStatements ) do

      -- check for value
      if entry.mainsnak.snaktype == 'value' then

         -- check for number
         if entry.mainsnak.datatype == 'quantity' then

            -- new data set
            entryValue = {}

            -- getting the amount, converted into a number
            entryValue.amount = tonumber ( entry.mainsnak.datavalue.value.amount )

            -- getting the unit
            entryValue.unit = entry.mainsnak.datavalue.value.unit:gsub( 'https?://www.wikidata.org/entity/', '' ) or ''

            -- save a copy of the original unit
            -- in case somebody wants to know, whether a value is converted
            entryValue.unitOriginal = entryValue.unit

            -- check, whether its the requested unit
            if entryValue.unit == requestedUnit then
               hasRequested = true
            end
            
            -- initialising qualifiers
            entryValue.year = 0
            
            -- read qualifiers
            if entry.qualifiers ~= nil then

               -- read P585-Qualifiers (date/time of validity)
               if entry.qualifiers.P585 ~= nil then
                  if entry.qualifiers.P585[1].datavalue.value.precision == 9 then
                     entryValue.year = tonumber ( entry.qualifiers.P585[1].datavalue.value.time:sub( 2, 5 ) )
                     entryValue.time = entry.qualifiers.P585[1].datavalue.value.time
                  end
                  if entry.qualifiers.P585[1].datavalue.value.precision == 10 then
                     entryValue.year = tonumber ( entry.qualifiers.P585[1].datavalue.value.time:sub( 2, 5 ) )
                     entryValue.time = entry.qualifiers.P585[1].datavalue.value.time
                  end
                  if entry.qualifiers.P585[1].datavalue.value.precision == 11 then
                     entryValue.year = tonumber ( entry.qualifiers.P585[1].datavalue.value.time:sub( 2, 5 ) )
                     entryValue.time = entry.qualifiers.P585[1].datavalue.value.time
                  end
               end
               
               -- read P2144-Qualifier (frequency)
               if entry.qualifiers.P2144 ~= nil then
                  if entry.qualifiers.P2144[1].datavalue ~= nil then
                     entryValue.P2144 = tonumber ( entry.qualifiers.P2144[1].datavalue.value.amount )
                  end
               end
               
            end

            referenceValues = {}

            -- read references
            if entry.references ~= nil then

               for j, referenceEntry in ipairs ( entry.references ) do
                  if referenceEntry.snaks ~= nil then
                     if referenceEntry.snaks.P854 ~= nil then
                        if referenceEntry.snaks.P854[1].snaktype == 'value' then
                           table.insert( referenceValues, referenceEntry.snaks.P854[1].datavalue.value )
                        end
                     end
                  end
               end -- for j, referenceEntry in ipairs ( entry.references )

            end -- if entry.references ~= nil

            entryValue.references = referenceValues

            -- adding to the list
            table.insert( wdValues, entryValue )

         end -- if entry.mainsnak.datatype == 'quantity'

      end -- if entry.mainsnak.snaktype == 'value'

   end -- for i, entry in ipairs ( wdStatements )


   -- nothing found then exit
   if wdValues == {} then
      return {}, localID
   end

   -- generating the returning values
   local returnValues = {}

   -- controll tag, for getting all or just one value
   local noFetch = false

   -- iterating variable
   local i = 1

   -- requested unit found?
   if hasRequested then

      -- getting the values, no conversion needed
      repeat

         -- getting a row
         entryValue = wdValues[i]

         -- put the entry to the list, if its the requested unit
         if entryValue.unit == requestedUnit then

            -- adding the row to the final result table
            table.insert( returnValues, entryValue )

            -- stopping, if only one value is requested
            if requestedValues == 'single' then
               noFetch = true
            end

         end

         -- counting up
         i = i + 1

      until noFetch or i > #wdValues

   -- requested unit not found
   -- trying conversion
   else
      
      -- tagging, if its succeeded at least once
      local conversionSuccess = true

      -- check, whether there is at least on data set
      if wdValues[i] ~= nil then

         -- initialize with false
         conversionSuccess = false

         -- getting the values and trying conversion
         repeat

            -- getting a row
            entryValue = wdValues[i]

            -- put the entry to the list, if the unit and conversion are available
            if units[entryValue.unit] ~= nil then
               if units[entryValue.unit].conversion[requestedUnit] ~= nil then

                  -- adding the row to the final result table
                  table.insert( 
                     returnValues, 
                     { 
                        amount = entryValue.amount * units[entryValue.unit].conversion[requestedUnit],
                        unit = requestedUnit,
                        unitOriginal = entryValue.unit,
                        year = entryValue.year
                     }
                  )

                  -- stopping, if only one value is requested
                  if requestedValues == 'single' then
                     noFetch = true
                  end

                  -- tagging as success
                  conversionSuccess = true

               end
            end

            -- counting up
            i = i + 1

         until noFetch or i > #wdValues

      end

   end -- if hasRequested then

   return returnValues, localID
 
end



-- getNumbersWithUnit() 
-- gets the amount of a property including its unit
--   first parameters: see internal function
--   values:
--      - single (standard): Only one (first) entry is shown
--      - all: All entries are fetched
--   precision: precision of the number (Standard: 0) 
--   show:
--      - number: formatted amount without unit
--      - plain: not formatted amount without unit
--      - short (standard): amount with short unit
--      - long: amount with long unit
--      - short-date: short with date
--      - short-date-ref: short with date and reference(s)
--   delimiter: delimiter between the numbers; standard is comma and breakable space
GetNumber.getNumbersWithUnit = function ( id, property, unit, values, precision, show, delimiter, frame )

   -- returning String
   local numberString = ''

   -- making empty parameters to nil
   if unit == '' then unit = nil end
   if values == '' then values = nil end

   -- WD-Values
   local numberList
   local wikidataID
   numberList, wikidataID = getNumbersAsTable ( id, property, unit, values )

   -- precision
   local numberPrecision = tonumber ( precision ) or 0

   -- show options
   local numberShow = show or 'short'

   -- no delimiter in front of the first entry
   local numberDelimiter = ''

   if numberList[1] == nil then
      return nil
   end

   -- displaying the values
   for i, entry in ipairs( numberList ) do

      -- no unit given or wanted
      if numberShow == 'number' then 
         numberString = numberString .. numberDelimiter .. lang:formatNum ( _round ( entry.amount, precision ) )
         
      elseif numberShow == 'plain' then 
         numberString = numberString .. numberDelimiter .. tostring ( entry.amount, precision )

      -- displaying with unit using its long name
      elseif numberShow == 'long' then

         if units[entry.unit] ~= nil then
            if _round ( entry.amount, precision ) == 1 then 
               numberString = numberString .. numberDelimiter .. lang:formatNum ( _round ( entry.amount, precision ) ) .. ' ' .. units[entry.unit].longSingle
            else
               numberString = numberString .. numberDelimiter .. lang:formatNum ( _round ( entry.amount, precision ) ) .. ' ' .. units[entry.unit].long
            end
         else
            numberString = numberString .. numberDelimiter .. lang:formatNum ( _round ( entry.amount, precision ) )
         end

      -- displaying with unit using its abbreviation
      elseif numberShow == 'short-date' then 

         if units[entry.unit] ~= nil then
            if entry.time ~= nil then
               numberString = numberString .. numberDelimiter .. lang:formatNum ( _round ( entry.amount, precision ) ) .. ' ' .. units[entry.unit].short .. ' <small>(' .. lang:formatDate( 'd.m.Y', entry.time ) .. ')</small>'
            else
               numberString = numberString .. numberDelimiter .. lang:formatNum ( _round ( entry.amount, precision ) ) .. ' ' .. units[entry.unit].short
            end
         else
            numberString = numberString .. numberDelimiter .. lang:formatNum ( _round ( entry.amount, precision ) )
         end

      -- displaying with unit using its abbreviation
      elseif numberShow == 'short-date-ref' then 

         if units[entry.unit] ~= nil then
            if entry.time ~= nil then
               numberString = numberString .. numberDelimiter .. lang:formatNum ( _round ( entry.amount, precision ) ) .. ' ' .. units[entry.unit].short .. ' <small>(' .. lang:formatDate( 'd.m.Y', entry.time ) .. ')</small>'
            else
               numberString = numberString .. numberDelimiter .. lang:formatNum ( _round ( entry.amount, precision ) ) .. ' ' .. units[entry.unit].short
            end
         else
            numberString = numberString .. numberDelimiter .. lang:formatNum ( _round ( entry.amount, precision ) )
         end

         if #entry.references > 0 then 
            for j, refEntry in ipairs ( entry.references ) do
               numberString = numberString .. frame:extensionTag{ name = 'ref', content = '[' .. refEntry .. ' ' .. refEntry .. ']' }
            end
         end

      -- displaying with unit using its abbreviation
      else 

         if units[entry.unit] ~=nil then
            numberString = numberString .. numberDelimiter .. lang:formatNum ( _round ( entry.amount, precision ) ) .. ' ' .. units[entry.unit].short
         else
            numberString = numberString .. numberDelimiter .. lang:formatNum ( _round ( entry.amount, precision ) )
         end

      end
      
      -- adding the frequency qualifikator, in case of mains voltage is requested
      if property == 'P2884' and entry.P2144 ~= nil then
         numberString = numberString .. ',&nbsp;' .. entry.P2144 .. '&#x202F;Hz'
      end

      -- setting the delimiter after the first entry
      numberDelimiter = delimiter or ', '
   end

   -- providing the list of numbers
   return numberString

end

-- getNumbersWithDate() 
-- gets the amount of a property including "valid at" date
--   first parameters: see internal function
--   values:
--      - single (standard): Only one (first) entry is shown
--      - all: All entries are fetched
--   show:
--      - number: Only the formatted number is shown (Standard)
--      - plain: Only the Not formatted number is shown
--      - year: the year of validity is shown
--   delimiter: delimiter between the numbers; standard is comma and breakable space
GetNumber.getNumbersWithDate = function ( id, property, values, show, delimiter )

   -- returning String
   local numberString = ''

   -- making empty parameters to nil
   if values == '' then values = nil end
   if show == '' then show = nil end

   -- setting to defaul, if necessary
   values = values or 'single'
   show = show or 'number'

   -- WD-Values
   local numberList
   local wikidataID
   -- always fetching all to be sorted later, because "singles" normally means the "most recent"
   numberList, wikidataID = getNumbersAsTable ( id, property, nil, 'all' )

   if numberList[1] == nil then
      return nil
   end

   --- Sorting the table by year
   table.sort(numberList, function(a,b) return a.year < b.year end)

   -- no delimiter in front of the first entry
   local numberDelimiter = ''

   -- displaying the values
   if values == 'single' then
      -- using the first table row
      if show == 'year' then
         numberString = lang:formatNum ( numberList[1].amount ) .. ' (' .. numberList[1].year .. ')'
      elseif show == 'plain' then
         numberString = numberList[1].amount
      else
         numberString = lang:formatNum ( numberList[1].amount )
      end
   else
      -- running through the table
      for i, entry in ipairs( numberList ) do

         -- adding an entry
         if show == 'year' then
            numberString = numberString .. numberDelimiter .. lang:formatNum ( entry.amount ) .. '&#x202F;<span class="voy-small">(' .. entry.year .. ')</span>'
         elseif show == 'plain' then
            numberString = numberString .. numberDelimiter .. tostring ( entry.amount )
         else
            numberString = numberString .. numberDelimiter .. lang:formatNum ( entry.amount )
         end

         -- setting the delimiter after the first entry
         numberDelimiter = delimiter or ', '
      end
   end

   -- providing the list of numbers
   return numberString

end



-- Providing template access
local p = {}

function p.getNumbersWithUnit( frame )
   return GetNumber.getNumbersWithUnit( frame.args[ 1 ], frame.args[ 2 ], frame.args[ 3 ], frame.args[ 4 ], frame.args[ 5 ], frame.args.show or frame.args[ 6 ], frame.args.delimiter or frame.args[ 7 ], frame ) or ""
end

function p.getNumbersWithDate( frame )
   return GetNumber.getNumbersWithDate( frame.args[ 1 ], frame.args[ 2 ], frame.args[ 3 ], frame.args.show or frame.args[ 4 ], frame.args.delimiter or frame.args[ 5 ] ) or ""
end

-- for usage in other modules
-- using it the following way:
--
-- local getNumber = require( 'Module:GetNumber' )
-- foo = getNumber.GetNumber().xxx( id, lang )
function p.GetNumber()
   return GetNumber
end

return p