Module:Term attestation
From Landrace.wiki
More actions
Reads the fields of {{Term attestation}}, shows the row and stores its subobject. String functions are not available on this wiki, so the checks are made here.
Functions
{{#invoke:Term attestation|row}}- Called by the template, which passes
source_titleandsource_display: the source page's Property:Has research title and its display title, looked up with#showunless the row givessource_label. The module reads the template's other fields itself. Returns one table row, with an anchorterm-followed by the row'sid, and stores one subobject of the same name, linked back to the page by Property:Has term page. A direct#invokemay give the fields itself.
Checks
- Not stored when
idis not lower-case letters, digits and hyphens;term,language,denotes,source,reforlocatoris empty;language_codeis not three lower-case letters; orsourceholds markup. The row shows each error and says that it is not stored. - Stored without the field when a transliteration has no
system; aconceptorplaceholds markup; thecoordinatesare not decimal degrees or have no place; theyearis not a single year (read by Module:Timeline);circahas no year; orderived_fromis notPage#idor lacks any ofetymology_source,etymology_refandetymology_locator. The row shows the error beside the field. - Values outside the allowed lists of Property:Has term denotes and Property:Has transliteration system are refused by the store, which marks them with a warning.
- The Source column links the source page under
source_label, else the research title, else the display title, else the page name. The label is display only. - Coordinates are rounded to two decimal places, as the timeline and waypoint subobjects round theirs. Bold and italic marks are removed from stored text.
-- {{Term attestation}}: one row of a table of recorded terms, and the term subobject behind it.
-- A term attestation records that one source, at one place in it, gives a word in a language with a meaning,
-- for a place and a year. It is stored only when its source, citation and locator are all given.
local p = {}
local Timeline = require('Module:Timeline')
local E = {
id = 'Give an id of lower-case letters, digits and hyphens, unique on the page',
term = 'Give the term as the source writes it',
language = 'Give the language by name',
code = 'Give the language as a three-letter ISO 639-3 code, for example ben; und when the source does not say',
denotes = 'Say what the term denotes',
source = 'Give the page of the source',
ref = 'Give the name of the citation of the source on this page',
locator = 'Give where in the source: page, answer or verse, and the words quoted',
system = 'Say which spelling system the transliteration follows',
name = 'Give the bare page title: the template makes the link',
year = 'Give a single year, negative before the Common Era',
circa = 'A circa mark needs a year',
coord = 'Coordinates must be decimal degrees, for example 26.85, 80.95',
nowhere = 'Coordinates need a place',
derived = 'Give the attestation it derives from as Page#id',
etymology = 'A derivation needs the source that states it: etymology_source, etymology_ref and etymology_locator',
}
local NOT_STORED = 'Not stored until the errors in this row are fixed'
local function err(key)
return '<span class="error">' .. E[key] .. '</span>'
end
local function trim(s)
return mw.text.trim(s or '')
end
-- Plain text for the store: the bold and italic quote marks are display only.
local function plain(s)
return (s:gsub("'''''", ''):gsub("'''", ''):gsub("''", ''))
end
-- A bare page title, or nil and the error key when the field holds markup.
local function title(s)
s = trim(s)
if s == '' then
return nil
end
if s:find('[%[%]{}|<>]') then
return nil, 'name'
end
return s
end
local function list(s)
local out = {}
for _, part in ipairs(mw.text.split(s or '', ';', true)) do
part = trim(part)
if part ~= '' then
table.insert(out, part)
end
end
return out
end
-- Two decimal places, as the timeline and waypoint subobjects round their points.
local function round2(x)
local r = math.floor(math.abs(x) * 100 + 0.5) / 100
if x < 0 then
r = -r
end
return r
end
local function showYear(y, circa)
local s = (y < 0) and ((-y) .. ' BCE') or tostring(y)
if circa then
s = 'c. ' .. s
end
return s
end
local function yes(s)
s = trim(s):lower()
return s == 'yes' or s == 'y' or s == 'true' or s == '1'
end
-- Read and check every field. Returns the record and the list of error keys, each tagged with the cell it
-- belongs to; `store` is false when a required field is missing or wrong.
function p._read(a, pagename)
local r, errs, store = {}, {}, true
local function bad(key, cell, blocks)
table.insert(errs, {key = key, cell = cell})
if blocks then
store = false
end
end
r.id = trim(a.id)
if not r.id:match('^[a-z0-9][a-z0-9%-]*$') then
bad('id', 'term', true)
end
r.term = trim(a.term)
if r.term == '' then
bad('term', 'term', true)
end
r.translit = trim(a.transliteration)
r.system = trim(a.system)
if r.translit ~= '' and r.system == '' then
bad('system', 'term', false)
end
r.language = trim(a.language)
if r.language == '' then
bad('language', 'language', true)
end
r.code = trim(a.language_code)
if not r.code:match('^%l%l%l$') then
bad('code', 'language', true)
end
r.denotes = list(a.denotes)
if #r.denotes == 0 then
bad('denotes', 'meaning', true)
end
r.gloss = trim(a.gloss)
local e
r.concept, e = title(a.concept)
if e then
bad(e, 'meaning', false)
end
r.places = {}
for _, s in ipairs(list(a.place)) do
local t, pe = title(s)
if t then
table.insert(r.places, t)
else
bad(pe, 'place', false)
end
end
local cs = trim(a.coordinates)
if cs ~= '' then
local lat, lon = cs:match('^(%-?%d+%.?%d*)%s*,%s*(%-?%d+%.?%d*)$')
lat, lon = tonumber(lat), tonumber(lon)
if not (lat and lon and math.abs(lat) <= 90 and math.abs(lon) <= 180) then
bad('coord', 'place', false)
elseif #r.places == 0 then
bad('nowhere', 'place', false)
else
r.coords = round2(lat) .. ',' .. round2(lon)
end
end
local ys = trim(a.year)
r.circa = yes(a.circa)
if ys ~= '' then
r.year = Timeline._year(ys)
if not r.year or r.year == 0 then
r.year = nil
bad('year', 'year', false)
end
elseif r.circa then
bad('circa', 'year', false)
end
r.source, e = title(a.source)
if e then
bad(e, 'source', true)
elseif not r.source then
bad('source', 'source', true)
end
r.ref = trim(a.ref)
if r.ref == '' or r.ref:find('[<>"|]') then
bad('ref', 'source', true)
end
r.locator = trim(a.locator)
if r.locator == '' then
bad('locator', 'source', true)
end
r.cite = trim(a.cite) ~= 'no'
-- An optional derivation, stored only with the source that states it.
local ds = trim(a.derived_from)
if ds ~= '' then
local dpage, did = ds:match('^(.-)#([a-z0-9][a-z0-9%-]*)$')
local dt
if dpage then
dt, e = title(dpage)
end
if not dpage or e then
bad('derived', 'term', false)
else
r.derived = {page = dt or pagename, id = did}
r.etymon = trim(a.etymon)
r.etym_source, e = title(a.etymology_source)
r.etym_ref = trim(a.etymology_ref)
r.etym_locator = trim(a.etymology_locator)
if e or not r.etym_source or r.etym_ref == '' or r.etym_ref:find('[<>"|]') or r.etym_locator == '' then
r.derived = nil
bad('etymology', 'term', false)
end
end
end
return r, errs, store
end
-- The values of the subobject, in order, as "Property=value" strings.
function p._values(r, pagename)
local v = {'@linkWith=Has term page'}
local function add(prop, val)
if val and val ~= '' then
table.insert(v, prop .. '=' .. val)
end
end
add('Has term', plain(r.term))
if r.system ~= '' then
add('Has term transliteration', plain(r.translit))
end
add('Has transliteration system', r.system)
add('Has term language', r.language)
add('Has term language code', r.code)
for _, d in ipairs(r.denotes) do
add('Has term denotes', d)
end
add('Has term gloss', plain(r.gloss))
add('Has term concept', r.concept)
for _, pl in ipairs(r.places) do
add('Has term place', pl)
end
add('Has coordinates', r.coords)
if r.year then
add('Has term year', tostring(r.year))
add('Has term year circa', r.circa and 'true' or 'false')
end
add('Has term source', r.source)
add('Has term ref name', r.ref)
add('Has term locator', plain(r.locator))
if r.derived then
add('Has term derived from', r.derived.page .. '#term-' .. r.derived.id)
add('Has etymology source', r.etym_source)
add('Has etymology ref name', r.etym_ref)
add('Has etymology locator', plain(r.etym_locator))
end
return v
end
local function cite(frame, name)
return frame:extensionTag('ref', '', {name = name})
end
-- A label for a link: plain text, entities decoded, no characters that would break the link.
local function label(s)
s = mw.text.trim(mw.text.decode(s or ''))
s = s:gsub('[%[%]{}|<>]', ''):gsub('%s+', ' ')
return s
end
-- The words the Source column shows for the source page: `source_label` when the row gives one, else the
-- title stored on the source page (Has research title, looked up by the template), else its display title
-- (Display title of), else the page name itself. Display only: the stored value is the page.
function p._sourceText(source, override, title, display)
local choices = {override, title, display}
for i = 1, 3 do
local s = label(choices[i])
if s ~= '' then
if s == source then
return '[[' .. source .. ']]'
end
return '[[' .. source .. '|' .. s .. ']]'
end
end
return '[[' .. source .. ']]'
end
-- {{#invoke:Term attestation|row}}, called from {{Term attestation}}: the table row and the subobject.
-- The fields are the template's; a direct #invoke may give them itself (used to test the module unsaved).
function p.row(frame)
local parent = frame:getParent()
local a = frame.args
if parent and (parent.args.id or parent.args.term or parent.args.source) then
a = parent.args
end
local pagename = mw.title.getCurrentTitle().prefixedText
local r, errs, store = p._read(a, pagename)
local notes = {term = {}, language = {}, meaning = {}, place = {}, year = {}, source = {}}
for _, x in ipairs(errs) do
table.insert(notes[x.cell], err(x.key))
end
local function cell(name, s)
if #notes[name] > 0 then
s = trim(s .. ' ' .. table.concat(notes[name], ' '))
end
return s
end
-- Term: the word as the source writes it, its transliteration, and a derivation if any.
local t = r.term ~= '' and r.term or ''
if r.translit ~= '' then
local tr = "''" .. r.translit .. "''"
if r.system ~= '' then
tr = '<span title="' .. mw.text.encode(r.system) .. '">' .. tr .. '</span>'
end
t = t .. '<br />' .. tr
end
if r.derived then
local here = r.derived.page == pagename
local target = (here and '' or r.derived.page) .. '#term-' .. r.derived.id
local label = r.etymon ~= '' and ("''" .. r.etymon .. "''") or 'an earlier form'
local via = here and '' or (' on [[' .. r.derived.page .. ']]')
t = t .. '<br /><small>From [[' .. target .. '|' .. label .. ']]' .. via .. ', per [[' ..
r.etym_source .. ']], ' .. r.etym_locator .. (r.cite and cite(frame, r.etym_ref) or '') .. '</small>'
end
local lang = r.language
if r.code ~= '' then
lang = lang .. ' <small>(' .. mw.text.nowiki(r.code) .. ')</small>'
end
local meaning = r.gloss
local kinds = table.concat(r.denotes, ', ')
if r.concept then
kinds = (kinds ~= '' and (kinds .. ': ') or '') .. '[[' .. r.concept .. ']]'
end
if kinds ~= '' then
meaning = (meaning ~= '' and (meaning .. '<br />') or '') .. '<small>' .. kinds .. '</small>'
end
local places = {}
for _, pl in ipairs(r.places) do
table.insert(places, '[[' .. pl .. ']]')
end
local year, sortkey = '', ''
if r.year then
year = showYear(r.year, r.circa)
sortkey = 'data-sort-value="' .. r.year .. '" | '
end
local src = ''
if r.source then
src = p._sourceText(r.source, a.source_label, frame.args.source_title, frame.args.source_display)
if r.locator ~= '' then
src = src .. ', ' .. r.locator
end
if r.cite and r.ref ~= '' and not r.ref:find('[<>"|]') then
src = src .. cite(frame, r.ref)
end
end
if not store then
table.insert(notes.source, '<span class="error">' .. NOT_STORED .. '</span>')
end
local anchor = r.id:match('^[a-z0-9][a-z0-9%-]*$') and (' id="term-' .. r.id .. '"') or ''
local out = {
'|-' .. anchor,
'| ' .. cell('term', t),
'| ' .. cell('language', lang),
'| ' .. cell('meaning', meaning),
'| ' .. cell('place', table.concat(places, ', ')),
'| ' .. sortkey .. cell('year', year),
'| ' .. cell('source', src),
}
local row = table.concat(out, '\n')
if store then
local args = {'term-' .. r.id}
for _, x in ipairs(p._values(r, pagename)) do
table.insert(args, x)
end
row = row .. frame:callParserFunction('#subobject', args)
end
return row
end
return p