Module:DataAggregation
Jump to navigation
Jump to search
Module documentation
This documentation is transcluded from Module:DataAggregation/doc. [edit] [history] [purge]
This module does not have any documentation. Please consider adding documentation at Module:DataAggregation/doc. [edit]
Module:DataAggregation's function aggregate is invoked by Template:DataTable.
Module:DataAggregation's function submissionLink is invoked by Template:DataSubmissionLink.
Module:DataAggregation's function toJSON is invoked by Template:DataSchema.
Module:DataAggregation requires Module:Addcommas.
Module:DataAggregation loads data from Module:Data/< ... >.
Module:DataAggregation loads data from Module:Schema/< ... >.
| Function list |
|---|
| L 11 — round L 22 — splitString L 36 — median L 50 — average L 66 — plural L 81 — confRange L 115 — runefontColour L 131 — getCharmsDropped L 156 — checkTitle L 180 — load L 203 — p.toJSON L 208 — p._toJSON L 223 — p.submissionLink L 228 — p._submissionLink L 244 — p.aggregate L 249 — p._aggregate L 296 — p.singlePercent L 324 — p.submissions L 363 — p.percentileTable L 470 — p.dropsTable L 608 — p.slimPercent L 697 — p.charmlog |
local p = {}
local commas = require('Module:Addcommas')._add
--
-- From Wikipedia:Module:Math
--
-- @param value {number} Original value
-- @param precision {number} Decimal places
-- @return {number} Number rounded to precision
--
local function round( value, precision )
local rescale = math.pow( 10, precision or 0 );
return math.floor( value * rescale + 0.5 ) / rescale;
end
--
-- Split the given quantity range into a table of quantities
--
-- @param range {string} Quantity range
-- @return {table} Table of quantities
--
local function splitString( range )
local t = {}
for str in string.gmatch( range, "([^-]+)" ) do
table.insert( t, str )
end
return t
end
--
-- Calculate the median
--
-- @param numlist {table} Table of numbers
-- @return {number} Median number
--
local function median( numlist )
table.sort( numlist )
if #numlist %2 == 0 then
return ( numlist[#numlist / 2] + numlist[#numlist /2 + 1] ) / 2
end
return numlist[math.ceil( #numlist / 2 )]
end
--
-- Calculaate the average
--
-- @param numlist {table} Table of numbers
-- @return {number} Average number
--
local function average( numlist )
local sum = 0
for _,v in pairs( numlist ) do -- Get the sum of all numbers in numlist
sum = sum + v
end
return sum / #numlist
end
--
-- Returns a singluar or plural based on quantity
--
-- @param value {number} Original value
-- @param singular {number} Singular text
-- @param plural {number} Plural text
-- @return {number} Number rounded to precision
--
local function plural( quantity, singular, plural )
if quantity == 1 then
return singular
else
return plural or singular..'s'
end
end
--
-- Calculate a confidence range
--
-- @param count {number} Drop count
-- @param total {number} Total drops count
-- @return {string, number, number} Confidence range text, lower end, upper end
--
local function confRange( count, total )
if total == 0 then
return 0
end
count = count / 1 --drop amount
local _z = 1.64485
local zsq = _z * _z
local _p = count / total
local n1 = 2 * total * _p + zsq
local n2 = _z * math.sqrt( zsq + ( 4 * total * _p * ( 1 - _p ) ) )
local _d = 2 * ( total + zsq )
local _l = ( 100 / _d ) * ( n1 - n2 )
local _u = ( 100 / _d ) * ( n1 + n2 )
local rnd
if _l < 1 then
rnd = 1
else
rnd = 0
end
local _lr = round( _l, rnd )
local _ur = round( _u, rnd )
if _lr == _ur then
return _lr..'%', _lr, _lr
else
return _lr..'–'.._ur..'%', _lr, _ur
end
end
--
-- Get the runefont css colour
--
-- @param quantity {number} Item stack quantity
-- @return {string} Runefont css colour
--
local function runefontColour( quantity )
if quantity < 100000 then
return 'yellow'
elseif quantity < 10000000 then
return 'white'
else
return 'lightgreen'
end
end
--
-- Get the number of charms dropped by a given monster
--
-- @param monster {string} The monster to check
-- @return {number|nil} Number of charms dropped if found, nil otherwise
--
function getCharmsDropped( monster )
local data = bucket('charm_drops')
.select('quantity')
.where('page_name_sub', monster)
.limit(1)
.run()
if #data > 0 then
return data[1].quantity
end
end
--
-- Map redirects to their correct pages
--
local pageRedirects = {
}
--
-- Makes sure first letter of page name is uppercase
-- Automatically handles any redirects
--
-- @param pageName {string} Page name to validate
-- @return {string} Validated page name
--
local function checkTitle( pageName )
-- upper case first letter to make sure we can find a valid page name
pageName = mw.ustring.gsub( pageName, '�?39;', "'" )
pageName = mw.ustring.gsub( pageName, '_', ' ' )
pageName = mw.ustring.gsub( pageName, ' +', ' ' )
pageName = mw.text.split( pageName, '' )
pageName[1] = mw.ustring.upper( pageName[1] )
pageName = table.concat( pageName )
-- automatically handle redirects
if pageRedirects[pageName] ~= nil then
pageName = pageRedirects[pageName]
end
return pageName
end
--
-- Simple mw.loadData wrapper used to access data located on module subpages
--
-- @param moduleType {string} Module type - Schema or Data
-- @param page {string} Page to retrieve data for
-- @return {table} Table of page data
--
local function load( moduleType, page )
page = checkTitle( page )
local noErr, ret
if (moduleType == "Data") then
noErr, ret = pcall(mw.loadData, "Module:Data/" .. page)
elseif (moduleType == "Schema") then
noErr, ret = pcall(mw.loadData, "Module:Schema/" .. page)
end
if noErr then
return ret
end
return nil
end
--
-- Converts a schema into json
--
-- @param frame {template} Template calling onto module
-- @return {string} Json string of chosen schema passed in from template
--
function p.toJSON( frame )
local args = frame:getParent().args
return p._toJSON( args )
end
function p._toJSON( args )
if args[1] == nil or args[1] == '' then
return ''
end
local schema = load( 'Schema', args[1] )
return mw.text.jsonEncode( schema )
end
--
-- Generates an add to log link
--
-- @param frame {template} Template calling onto module
-- @return {string} The link
--
function p.submissionLink( frame )
local args = frame:getParent().args
return p._submissionLink( args )
end
function p._submissionLink( args )
local schemaType = args.schema or mw.title.getCurrentTitle().text
local page = args.page or mw.title.getCurrentTitle().text
local quantity = tonumber( args.quantity ) or 1
local text = args.text or 'Add data to the log'
local url = tostring( mw.uri.fullUrl( mw.title.getCurrentTitle().fullText, { action = 'view', logEdit = 'true' } ) )
local link = '<div class="datatable" data-schema="'..schemaType..'" data-quantity="'..quantity..'" data-page="'..page..'">['..url..' '..text..'] (requires JavaScript)</div>'
return link
end
--
-- Entry point to aggregate the data
--
-- @param frame {template} Template calling onto module
-- @return {table} Aggregated data
--
function p.aggregate( frame )
local args = frame:getParent().args
return p._aggregate( args )
end
function p._aggregate( args )
local view = string.lower( args.view ) or 'percent'
local schemaType = args.schema or mw.title.getCurrentTitle().text
local page = args.page or mw.title.getCurrentTitle().text
local quantity = tonumber( args.quantity ) or 1
local schema = load( 'Schema', schemaType )
local data = load( 'Data', page )
local field = args.schemaField
local hasNothingDrop = (args.hasNothingDrop or ''):lower() == 'yes'
local version = args.version or ''
local bucket = args.bucket or ''
-- Set up categories
local ns = mw.title.getCurrentTitle().namespace
local categories = ''
if ns == 0 then
categories = categories..'[[Category:User submitted data]]'
end
-- Aggregated using the selected method
if view == 'percent' then
return tostring( p.percentileTable( schemaType, schema, page, quantity, data, hasNothingDrop ) )..categories
elseif view == 'drops' then
return p.dropsTable( schemaType, schema, page, quantity, data, hasNothingDrop, version, bucket )..categories
elseif view == 'singlepercent' then
return p.singlePercent( schema, quantity, data, field )
elseif view == 'slimpercent' then
return p.slimPercent( schemaType, schema, page, quantity, data )
elseif view == 'submissions' then
return p.submissions( schema, data )
elseif view == 'charmlog' then
return p.charmlog( schema, page, data )
elseif view == 'charmqty' then
return getCharmsDropped( monster )
else
return 'View not recognised. Valid views are "percent", "drops", "singlepercent", "submissions", and "charmlog".'
end
end
--
-- Calculate the percentage chance of a single item being received
--
-- @param schema {table} The schema for the module
-- @param quantity {number} Quantity of items dropped in one go - i.e 2 for Waterfiend (Ghorrock), 5 for Vorago
-- @param data {table} The existing submissions
-- @param field {field} The schema field to calculate on
-- @return {string} The percentage chance
function p.singlePercent( schema, quantity, data, field )
-- Set up the counts
local counts = {}
counts['total'] = 0
counts[field] = 0
-- Iterate the data and add up the total and field counts
for index, entry in pairs( data ) do
for key, value in pairs( schema.fields ) do
if value.name == 'total' or value.name == field then
counts[value.name] = counts[value.name] + ( entry[value.name] / ( value.quantity or 1 ) )
end
end
end
-- Calculate the percentage
local percent = math.floor( 1000 * ( counts[field] / ( counts['total'] * quantity ) ) + 0.5 ) / 10
return percent..'%'
end
--
-- Build the table to show all submissions for a module
--
-- @param schema {table} The schema for the module
-- @param data {table} The existing submissions
-- @return {table} Table containing each submission
--
function p.submissions( schema, data )
-- Create the table
local ret_table = mw.html.create( 'table' ):addClass( 'wikitable' ):addClass( 'sortable' )
:tag( 'tr' )
-- Load the headers from the schema
for key, value in pairs( schema.fields ) do
ret_table:tag( 'th' ):wikitext( value.label ):done()
end
-- Add the username and timestamp headers
ret_table:tag( 'th' ):wikitext( 'Username' ):done()
ret_table:tag( 'th' ):wikitext( 'Timestamp' ):done()
-- Load the data
for index, entry in pairs( data ) do
ret_table:tag( 'tr' )
for key, value in pairs( schema.fields ) do
ret_table:tag( 'td' ):wikitext( entry[value.name] ):done()
end
-- Add the username and timestamp data
ret_table:tag( 'td' ):wikitext( '[[Special:Contributions/'..entry['username']..'|'..entry['username']..']]' ):done()
ret_table:tag( 'td' ):wikitext( entry['timestamp'] ):done()
end
return ret_table:done()
end
--
-- Build the table to show confidence ranges for each drop
--
-- @param schemaType {string} The data schema type
-- @param schema {table} The data schema
-- @param page {string} The page the data module relates to
-- @param quantity {number} Quantity of items dropped in one go - i.e 2 for Waterfiend (Ghorrock), 5 for Vorago
-- @param data {table} Existing submissions
-- @param hasNothingDrop {boolean} Does the table have a nothing drop
-- @return {table} Aggregated data
--
function p.percentileTable( schemaType, schema, page, quantity, data, hasNothingDrop )
local counts = {}
-- Create the table
local ret_table = mw.html.create( 'table' ):addClass( 'wikitable' ):addClass( 'datatable' ):attr('data-schema', schemaType ):attr('data-quantity', quantity ):attr( 'data-page', page ):attr( 'data-nothing', tostring(hasNothingDrop) )
:tag( 'tr' )
-- Load the headers from the schema
local totalColumns = 1
ret_table:tag( 'th' ):css( { ['min-width'] = '40px' } ):wikitext( 'Nothing' ):done()
for key, value in pairs( schema.fields ) do
if value.name ~= 'evidence' and value.name ~= 'level' then
counts[value.name] = 0
if value.name ~= 'total' then
totalColumns = totalColumns + 1
local valQty = (string.lower(schemaType) == 'charms') and quantity or value.quantity
-- Temp fix - display all runefont in default yellow
local item = '<div style="position:relative">'..
'<span style="pointer-events:none;position:absolute;margin-top:-7px;margin-left:-3px;font-weight:400;font-family: \'RuneScape Small\';color:'..runefontColour( 1 )..';font-size:16px;text-shadow:#000 1px 1px;">'..valQty..'</span>'..
'[[File:'..value.icon..'|link='..value.page..']]'..
'</div>'
ret_table:tag( 'th' ):css( { ['min-width'] = '40px' } ):wikitext( item ):done()
end
end
end
local total = 0
if data ~= nil then
-- Calculate the total counts
for index, entry in pairs( data ) do
for key, value in pairs( schema.fields ) do
if value.name ~= 'evidence' and value.name ~= 'level' then
local amounts = splitString( value.quantity or 1 )
local quant = average( amounts )
local entryAmount = entry[value.name] or 0
counts[value.name] = counts[value.name] + ( entryAmount / quant )
end
end
end
-- Calculate the nothing count
counts['nothing'] = counts['total'] * quantity
for key, value in pairs( counts ) do
if key ~= 'total' and key ~= 'nothing' then
counts['nothing'] = counts['nothing'] - value
end
end
-- Add the percentages or no data row
ret_table:tag( 'tr' ):done()
total = counts['total']
if total == 0 then
ret_table:tag( 'td' )
:attr( 'colspan', totalColumns )
:css({ ['text-align'] = 'left' } )
:wikitext( 'There is currently no data for '..page..'.<br>Please help the wiki by submitting some.' )
:done()
else
local nothingRange = confRange( counts['nothing'] / quantity, total )
ret_table:tag( 'td' ):css( { ['text-align'] = 'center' } ):wikitext( nothingRange ):attr('title', commas( counts['nothing'] ) ):done()
for key, value in pairs( schema.fields ) do
if value.name ~= 'total' and value.name ~= 'evidence' and value.name ~= 'level' then
local count = counts[value.name]
local range = confRange( count / quantity, total )
ret_table:tag( 'td' ):css( { ['text-align'] = 'center' } ):wikitext( range ):attr('title', commas( count ) ):done()
end
end
end
end
-- Add the info to the bottom of the table
ret_table:tag( 'tr' ):done()
if data == nil then
ret_table:tag( 'td' )
:attr( 'colspan', totalColumns )
:css( { ['text-align'] = 'left' } )
:wikitext( 'Log data appears to be missing for '..page..'.<br>If you believe this is an error, please contact an administrator.' )
:done()
else
local url = tostring( mw.uri.fullUrl( mw.title.getCurrentTitle().fullText, { action = 'view', logEdit = 'true' } ) )
ret_table:tag( 'td' )
:attr( 'colspan', totalColumns )
:css( { ['font-size'] = 'smaller', ['text-align'] = 'left', ['line-height'] = '15px' } )
:wikitext( 'Represents a 90% confidence range based on a sample of '..commas( total )..' '..schema.sample..'.<br>'..
quantity..' '..plural( quantity, 'item is', 'items are' )..' dropped at a time.<br>'..
'['..url..' Add data to the log] (requires JavaScript).' )
:done()
end
return ret_table:done()
end
--
-- Build the table using the DropsLine template to show drop quantities and rarities
--
-- @param schemaType {string} The data schema type
-- @param schema {table} The data schema
-- @param page {string} The page the data module relates to
-- @param totalRolls {number} Number of items rolled in one go - i.e 2 for Waterfiend (Ghorrock), 5 for Vorago
-- @param data {table} Existing submissions
-- @param hasNothingDrop {boolean} Does the table have a nothing drop
-- @param version {string} Name to use for versioned drops table (if any)
-- @param bucket {string} no = don't save to bucket
-- @return {table} Aggregated data
--
function p.dropsTable( schemaType, schema, page, totalRolls, data, hasNothingDrop, version, bucket )
local counts = {}
local retVal = '';
mw.log(hasNothingDrop)
-- Set up the total to begin
counts['total'] = 0
-- Add the drops table head template
local url = tostring( mw.uri.fullUrl( mw.title.getCurrentTitle().fullText, { action = 'view', logEdit = 'true' } ) )
retVal = '{{DataTableHead|schemaType='..schemaType..'|quantity='..totalRolls..'|page='..page..'|hasNothingDrop='..tostring(hasNothingDrop)..'|version='..version..'|bucket='..bucket..'}}'
if data ~= nil then
for key, value in pairs( schema.fields ) do
if value.name ~= 'evidence' and value.name ~= 'level' then
counts[value.name] = 0
end
end
-- Calculate the total counts
for index, entry in pairs( data ) do
for key, value in pairs( schema.fields ) do
if value.name ~= 'evidence' and value.name ~= 'level' and value.type == 'number' then
local amounts = splitString( value.quantity or 1 )
local quant = average( amounts )
local entryAmount = entry[value.name] or 0
counts[value.name] = counts[value.name] + ( entryAmount / quant )
end
end
end
-- Add each drops line
for key, value in pairs( schema.fields ) do
if value.name ~= 'evidence' and value.name ~= 'total' and value.name ~= 'level' and value.type == 'number' then
local rarity = round( counts[value.name] ) .. '/' .. (counts['total'] * ( value.rolls or totalRolls ))
-- Is the item noted
local quantity = value.quantity
if value.noted == true then
quantity = quantity..' (noted)'
end
-- Set up the notes
local nameNotes = value.nameNotes or ''
local rarityNotes = value.rarityNotes or ''
local citations = value.citations or ''
-- Use the override rarity if we have one otherwise use estimated rarity if total count under threshold, or individual item count under threshold
local isApprox = true
if value.overrideRarity then
rarity = value.overrideRarity
isApprox = false
elseif counts['total'] < 1000 or counts[value.name] == 0 then
if value.estimatedRarity then
-- We have an estimated rarity
rarity = value.estimatedRarity
rarityNotes = rarityNotes..'{{DropNote|name=estimated|Using an estimated rarity as not enough data has been submitted.}}'
isApprox = false
elseif counts[value.name] == 0 then
-- We have no estimated rarity and no submissions for this item
rarity = 'Unknown'
rarityNotes = rarityNotes..'{{DropNote|name=unknown|No estimated rarity or submissions found for this item.}}'
isApprox = false
else
-- We have no estimated rarity but we have submissions so warn user the rarity may be inaccurate
rarityNotes = rarityNotes..'{{DropNote|name=inaccurate|Estimated rarity may be inaccurate due to a low sample size.}}'
end
end
-- Set the rolls param
local rolls = ( value.rolls or totalRolls ~= 1 ) and ( value.rolls or totalRolls ) or ''
-- Set the approx param
local approx = isApprox and 'yes' or ''
-- Set the template we are using
local template = schema.dropsLine or 'DropsLine'
-- Output the item using the DropsLine template
if value.name == 'triskelionFragment' then
-- Split up triskelionFragment into each part
rarityNotes = rarityNotes..'{{DropNote|name=triskelion|You will receive whichever fragment is next to the last crystal triskelion fragment you received.}}'
for i = 1, 3 do
retVal = string.format(
"%s\n{{%s|name=%s|quantity=%s|rarity=%s|rarityNotes=%s|rolls=%s|approx=%s|bucket=%s}}",
retVal,
template,
"Crystal triskelion fragment " .. i,
quantity,
rarity,
rarityNotes,
rolls,
approx,
value.bucket or ''
)
end
else
-- All other items
retVal = string.format(
"%s\n{{%s|name=%s|namenotes=%s|quantity=%s|rarity=%s|raritynotes=%s|altvalue=%s|altcurrency=%s|image=%s|rolls=%s|approx=%s|citations=%s|bucket=%s}}",
retVal,
template,
value.page,
nameNotes,
quantity,
rarity,
rarityNotes,
value.altValue or '',
value.altCurrency or '',
value.icon,
rolls,
approx,
citations,
value.bucket or ''
)
end
end
end
-- Add the drops table bottom
retVal = retVal..'{{DataTableBottom|size='..counts['total']..'|sample='..schema.sample..'|link='..url..'|schema='..schemaType..'|data='..page..'}}'
else
-- No data found for this page so display an error and close the table
retVal = retVal..'<tr><td colspan="7">Log data appears to be missing for '..page..'.<br>If you believe this is an error, please contact an administrator.</td></tr></table>'
end
return mw.getCurrentFrame():preprocess( retVal )
end
--
-- Build the table using the to display 3 columns - image, item and percentage
--
-- @param schemaType {string} The data schema type
-- @param schema {table} The data schema
-- @param page {string} The page the data module relates to
-- @param quantity {number} Quantity of items dropped in one go - i.e 2 for Waterfiend (Ghorrock), 5 for Vorago
-- @param data {table} Existing submissions
-- @return {table} Aggregated data
--
function p.slimPercent( schemaType, schema, page, quantity, data )
local counts = {}
-- Create the table
local ret_table = mw.html.create( 'table' ):addClass( 'wikitable' ):addClass( 'datatable' ):attr('data-schema', schemaType ):attr('data-quantity', quantity ):attr( 'data-page', page )
-- Load the counts
for key, value in pairs( schema.fields ) do
if value.name ~= 'evidence' and value.name ~= 'level' then
counts[value.name] = 0
end
end
local total = 0
if data ~= nil then
-- Calculate the total counts
for index, entry in pairs( data ) do
for key, value in pairs( schema.fields ) do
if value.name ~= 'evidence' and value.name ~= 'level' then
local amounts = splitString( value.quantity or 1 )
local quant = average( amounts )
local entryAmount = entry[value.name] or 0
counts[value.name] = counts[value.name] + ( entryAmount / quant )
end
end
end
-- Calculate the nothing count
counts['nothing'] = counts['total'] * quantity
for key, value in pairs( counts ) do
if key ~= 'total' and key ~= 'nothing' then
counts['nothing'] = counts['nothing'] - value
end
end
-- Add the percentages or no data row
ret_table:tag( 'tr' ):done()
total = counts['total']
if total == 0 then
ret_table:tag( 'td' )
:attr( 'colspan', 3 )
:css({ ['text-align'] = 'left' } )
:wikitext( 'There is currently no data for '..page..'.<br>Please help the wiki by submitting some.' )
:done()
else
for key, value in pairs( schema.fields ) do
if value.name ~= 'total' and value.name ~= 'evidence' and value.name ~= 'level' then
local count = counts[value.name]
local range = confRange( count / quantity, total )
ret_table:tag( 'tr' )
:tag( 'td' ):wikitext( '[[File:'..value.icon..']]' ):done()
:tag( 'td' ):wikitext( '[['..value.page..']]' ):done()
:tag( 'td' ):wikitext( range ):done()
:done()
end
end
end
end
-- Add the info to the bottom of the table
ret_table:tag( 'tr' ):done()
if data == nil then
ret_table:tag( 'td' )
:attr( 'colspan', 3 )
:css( { ['text-align'] = 'left' } )
:wikitext( 'Log data appears to be missing for '..page..'.<br>If you believe this is an error, please contact an administrator.' )
:done()
else
local url = tostring( mw.uri.fullUrl( mw.title.getCurrentTitle().fullText, { action = 'view', logEdit = 'true' } ) )
ret_table:tag( 'td' )
:attr( 'colspan', 3 )
:css( { ['font-size'] = 'smaller', ['text-align'] = 'left', ['line-height'] = '15px' } )
:wikitext( 'Represents a 90% confidence range based on a sample of '..commas( total )..' '..schema.sample..'.<br>'..
quantity..' '..plural( quantity, 'item is', 'items are' )..' dropped at a time.<br>'..
'['..url..' Add data to the log] (requires JavaScript).' )
:done()
end
return ret_table:done()
end
--
-- Build the table row to show information about charms dropped by a given monster
--
-- @param schema {table} The schema for the module
-- @param page {string} The page the data module relates to
-- @param data {table} The existing submissions
-- @return {table} Table row containing the charm information
--
function p.charmlog( schema, page, data )
if not data then
return error('Failed to load data for monster "'..page..'"')
end
local ret_row = mw.html.create( 'tr' )
local quantity = getCharmsDropped( page )
local counts = {
total = 0,
gold = 0,
green = 0,
crimson = 0,
blue = 0,
}
if not quantity then
mw.log( 'Error asking for charms dropped; assuming 1' )
quantity = 1
end
-- Iterate the data and add up the total and charm counts
for _, entry in pairs( data ) do
for key, value in pairs( counts ) do
counts[key] = counts[key] + entry[key]
end
end
local cr = {
gold = { confRange( counts.gold / quantity, counts.total ) },
green = { confRange( counts.green / quantity, counts.total ) },
crimson = { confRange( counts.crimson / quantity, counts.total ) },
blue = { confRange( counts.blue / quantity, counts.total ) },
}
-- field order (see [[Template:Charm log]]):
-- Monster, kills logged, gold %, green %, crimson %, blue %, charms per monster
ret_row
:tag( 'td' ):wikitext( '[['..page..']]' ):done()
:tag( 'td' ):wikitext( commas( counts.total ) ):done()
:tag( 'td' ):wikitext( cr.gold[1] ):attr( 'data-sort-val', cr.gold[2] ):done()
:tag( 'td' ):wikitext( cr.green[1] ):attr( 'data-sort-val', cr.green[2] ):done()
:tag( 'td' ):wikitext( cr.crimson[1] ):attr( 'data-sort-val', cr.crimson[2] ):done()
:tag( 'td' ):wikitext( cr.blue[1] ):attr( 'data-sort-val', cr.blue[2] ):done()
:tag( 'td' ):wikitext( quantity ):done()
return ret_row
end
return p