local mArguments -- initialize lazily
local mCommon = require( 'Module:Common' )
local floatingui = require( 'Module:FloatingUI' )
local data = mw.loadJsonData( 'Module:Cite DTP/data.json' )
local p = {}
-- Detect and strip a trailing -fn marker for parsing,
-- while returning a flag to allow special display / URL behaviour.
local function parseFootnoteSuffix( dtpId )
if not dtpId then
return dtpId, false
end
dtpId = mw.text.trim( dtpId )
if mw.ustring.match( dtpId, '%-fn$' ) then
return mw.ustring.gsub( dtpId, '%-fn$', '' ), true
end
return dtpId, false
end
--- Split DTP ID into a table of string
---
--- @param dtpId string - ID used for DTP
--- @return table
local function splitDTP( dtpId )
local temp = mCommon.splitStringIntoTable( dtpId, ' ' )
if not temp[2] then
return temp
end
local parts = mCommon.splitStringIntoTable( temp[2], '.' )
table.insert( parts, 1, temp[1] )
mw.logObject( parts, '[Cite DTP] DTP is segemented into' )
return parts
end
--- Return URL to Cite Tolkien
---
--- @param dtpParts table - Table of DTP ID parts
--- @return string
local function getDTPUrl( dtpParts )
local path = string.format(
'/%s/%s',
dtpParts[1],
string.sub( table.concat( dtpParts, '.' ), #dtpParts[1] + 2 )
)
-- Always include trailing slash so suffix logic is reliable
return 'https://cite.digitaltolkien.com' .. path .. '/'
end
--- Get the matching type table from the DTP part
---
--- @param part string - The current part of the DTP
--- @param schema table - The schema field of the current part
--- @return table|nil
local function getTypeData( part, schema )
if type( schema ) == 'table' then
for _, v in ipairs( schema ) do
local currType = data['types'][v]
local typePattern = '^' .. currType['pattern'] .. '$'
if string.match( part, typePattern ) then
local copy = {}
for j, x in pairs( currType ) do copy[j] = x end
copy['key'] = v
return copy
end
end
error( string.format( 'No matching type found for %s', part ) )
end
local matchedType = data['types'][schema]
if not matchedType then
error( string.format( 'No matching type definition for %s', schema ) )
end
local pattern = '^' .. matchedType['pattern'] .. '$'
if string.match( part, pattern ) then
local copy = {}
for j, x in pairs( matchedType ) do copy[j] = x end
copy['key'] = schema
return copy
end
end
--- Build a table of citation info from the DTP ID
---
--- @param dtpParts table - Table of DTP ID parts
--- @return table|nil
function p.buildCiteData( dtpParts )
local cite = {}
local matchedWork
local n = 1 -- schema index (schema are ordered by i - 1)
for i, part in pairs( dtpParts ) do
if i == 1 then
matchedWork = mCommon.tableContainsKey( data['works'], part )
if not matchedWork then
error( string.format( 'No matching work for %s', part ) )
elseif not matchedWork['author'] then
error( string.format( 'Missing author for %s', part ) )
elseif not matchedWork['label'] then
error( string.format( 'Missing label for %s', part ) )
elseif not matchedWork['schema'] then
error( string.format( 'Missing schema for %s', part ) )
end
local authors = {}
for _, author in pairs( matchedWork['author'] ) do
table.insert( authors, string.format( '[[%s]]', author ) )
end
cite['author'] = table.concat( authors, ', ' )
cite['work'] = string.format( '[[%s]]', matchedWork['label'] )
cite['order'] = { 'author', 'work' }
else
if not matchedWork['schema'][n] then
error( string.format( 'Missing schema #%s for %s', n, matchedWork['label'] ) )
end
local matchedType = getTypeData( part, matchedWork['schema'][n] )
if not matchedType then
error( string.format( '%s is not a valid input for field %d', part, n ) )
end
local typeKey = matchedType['key']
local key = part
local numKey = tonumber( part ) -- strips leading zeros
-- Default label from type (may be blank for paragraph in data.json)
local label = string.gsub( matchedType['label'] or '', '%$1', numKey or key )
-- SPECIAL CASE: paragraphs must render as "Paragraph N"
if typeKey == 'paragraph' then
label = string.format( 'Paragraph %s', tostring( numKey or key ) )
end
-- Custom labels from work data (chapters/books/etc.)
if matchedWork[typeKey] then
local match
local partKey = dtpParts[n]
if tonumber( dtpParts[n] ) then
partKey = tonumber( dtpParts[n] )
end
if type( matchedWork[typeKey][numKey or key] ) == 'string' then
match = matchedWork[typeKey][numKey or key]
elseif
matchedWork[typeKey][partKey] and
matchedWork[typeKey][partKey][numKey or key] and
type( matchedWork[typeKey][partKey][numKey or key] ) == 'string'
then
match = matchedWork[typeKey][partKey][numKey or key]
end
if match then
label = match
label = string.format( '"[[%s]]"', label )
end
end
-- If label is empty (and not a paragraph, which we force), do not add it
if label and mw.text.trim( label ) ~= '' then
cite[typeKey] = label
table.insert( cite['order'], typeKey )
end
n = n + 1
end
end
mw.logObject( cite, string.format( '[Cite DTP] Citation data of [%s]', table.concat( dtpParts, '.' ) ) )
return cite
end
--- Get the HTML for the DTP identifier in the citation
---
--- @param dtpUrl string - URL to the DTP site
--- @param dtpId string - DTP ID
--- @return mw.html
local function getIdentifierHTML( dtpUrl, dtpId )
local html = mw.html.create( 'span' )
:addClass( 'cite-dtp-identifier' )
:wikitext( string.format( '[%s %s]', dtpUrl, dtpId ) )
local tooltipHtml = mw.html.create( 'span' )
:addClass( 'cite-dtp-identifier-tooltip' )
:wikitext( string.format( '[%s %s]',
'https://digitaltolkien.com/citation-systems',
'Digital Tolkien Project Citation Systems'
) )
return floatingui.render( {
reference = html,
content = tooltipHtml
} )
end
-- Only insert a cite part if it has visible content
local function insertCitePart( cite, key, value )
if not value then
return
end
local trimmed = mw.text.trim( tostring( value ) )
if trimmed == '' then
return
end
local spanHtml = mw.html.create( 'span' )
spanHtml:addClass( 'cite-dtp-' .. key )
spanHtml:attr( 'title', key )
spanHtml:wikitext( value )
table.insert( cite, tostring( spanHtml ) )
end
-- Implements {{Cite DTP}} from the frame
function p.citeDTP( frame )
mArguments = require( 'Module:Arguments' )
return p._citeDTP( mArguments.getArgs( frame ), frame )
end
function p._citeDTP( args, frame )
if not args then
return 'Missing arguments'
end
local dtpIdRaw = args['id'] or args[1]
local dtpIdBase, isFootnote = parseFootnoteSuffix( dtpIdRaw )
local dtpParts = splitDTP( dtpIdBase )
local dtpUrl = getDTPUrl( dtpParts )
-- If -fn used, make the link include it: .../LR/6.08.219-fn/
if isFootnote then
dtpUrl = mw.ustring.gsub( dtpUrl, '/$', '-fn/' )
end
local cite = {}
local citeData = p.buildCiteData( dtpParts )
for _, key in ipairs( citeData['order'] ) do
insertCitePart( cite, key, citeData[key] )
end
-- Append "footnote" at the end (after paragraph etc.)
if isFootnote then
local spanHtml = mw.html.create( 'span' )
spanHtml:addClass( 'cite-dtp-footnote' )
spanHtml:attr( 'title', 'footnote' )
spanHtml:wikitext( 'footnote' )
table.insert( cite, tostring( spanHtml ) )
end
local wikitext = table.concat( cite, ', ' )
local html = mw.html.create( 'cite' ):addClass( 'citation cite-dtp' )
html:node( getIdentifierHTML( dtpUrl, dtpIdRaw ) )
html:wikitext( wikitext )
return table.concat( {
frame:extensionTag {
name = 'templatestyles', args = { src = 'Module:Cite DTP/styles.css' }
},
floatingui.load(),
tostring( html )
} )
end
return p