Module:SscCitations: Difference between revisions
From Bodhicitta
(VSCode edit) |
(VSCode edit) |
||
| Line 66: | Line 66: | ||
-- Every quote segment naming this id, grouped into quotations. | -- Every quote segment naming this id, grouped into quotations. | ||
local function fetchQuotations(id) | local function fetchQuotations(id) | ||
-- | -- Every quote segment, filtered in Lua rather than by the query. | ||
-- | -- | ||
-- | -- A wildcard on QuoteSource cannot do this correctly. "~*RKTSK 12*" also | ||
-- | -- matches RKTSK 127 and RKTSK 129, and anchoring on the closing parenthesis | ||
-- quotations | -- to fix that then MISSES segments where the id is not last, because a | ||
-- id | -- segment may name several sources at once: | ||
-- | |||
-- Bodhisattvapratimokṣa… (RKTSK 248);Vinayaviniścayopāli… (RKTSK 68) | |||
-- | |||
-- That combination silently halved the Vinayaviniścayopāliparipṛcchāsūtra | |||
-- (13 quotations reported as 6) and zeroed the Pañcaviṃśatisāhasrikā. | |||
-- Fetching the quote segments and matching "(id)" in Lua handles both. | |||
local query = mw.smw.ask{ | local query = mw.smw.ask{ | ||
'[[' .. SSC_PREFIX .. ']][[QuoteSource:: | '[[' .. SSC_PREFIX .. ']][[QuoteSource::+]]', | ||
'?QuoteSource#=QuoteSource', | |||
'?#-=Page', | '?#-=Page', | ||
'?SegmentOrder#=SegmentOrder', | '?SegmentOrder#=SegmentOrder', | ||
| Line 85: | Line 91: | ||
if not query then return {} end | if not query then return {} end | ||
local needle = '(' .. id .. ')' | |||
local segments = {} | local segments = {} | ||
for _, row in ipairs(query) do | for _, row in ipairs(query) do | ||
local order = tonumber(firstValue(row.SegmentOrder)) | local order = tonumber(firstValue(row.SegmentOrder)) | ||
if order then | local source = firstValue(row.QuoteSource) | ||
if order and type(source) == 'string' | |||
and source:find(needle, 1, true) then | |||
local text = firstValue(row.SegmentTranslation) | local text = firstValue(row.SegmentTranslation) | ||
local words = 0 | local words = 0 | ||
Revision as of 17:52, 25 August 2026
Documentation for this module may be created at Module:SscCitations/doc
local p = {}
-- Citations of a text in the Śikṣāsamuccaya.
--
-- The SSC is segmented and aligned in the Translation Memory namespace, one page
-- per segment. A segment that quotes another work carries SegmentFormat=Quote
-- and a QuoteSource naming the work, usually with its RKTS catalogue number:
--
-- QuoteSource=Ratnameghasūtra (RKTSK 231)
--
-- Text pages hold the same identifier in DrlPageName ("RKTSK 231"), so that is
-- the join: no title matching, unaffected by spelling, and it keeps Kangyur
-- (RKTSK) and Tengyur (RKTST) texts distinct. 60 of the 63 works the SSC quotes
-- have a page here, covering 92% of quote segments.
--
-- THE POINT OF THIS MODULE: one quotation is often split across several
-- consecutive segments. Counting segments therefore overstates the citations
-- badly — the Pitāputrasamāgamanasūtra has 62 quote segments but is quoted 14
-- times, and one run is 36 segments long. Across the SSC, 633 segments are 345
-- quotations. SMW has no notion of "consecutive", so the grouping is done here.
-- The SSC page itself. Its own segments quote it 83 times through internal
-- cross-references, which is an artefact of the alignment rather than a
-- citation of one work by another, so the block is suppressed there.
local SSC_PAGE = 'Texts/Śikṣāsamuccaya'
-- The bilingual layout of the SSC translation, one page per chapter. The
-- chapter number is the last path element.
local BILINGUAL_BASE = 'Books/The Training Anthology of Śāntideva/Bilingual/'
local SSC_PREFIX = '~Translation Memory:016-Tsadra-SSC-Root/*'
local SEGMENT_LIMIT = 1000
-- A quotation past this many words of translated text is flagged as long.
-- Word count, not segment count: segments vary wildly in size, and the single
-- longest quotation in the corpus (Ratnolkādhāraṇī, 8,330 words) is ONE
-- segment, so a segment count would have called it small. The median quotation
-- is 139 words and the 75th percentile 218, so 400 is clearly beyond normal
-- without being so rare it never shows.
local LONG_QUOTE_WORDS = 400
local function firstValue(v)
if type(v) == 'table' then return v[1] end
return v
end
-- The RKTS id for this page, e.g. "RKTSK 231". Absent on anything that is not a
-- canonical text, which is what keeps the feature off every other page without
-- needing a flag.
local function rktsId(frame)
local title = mw.title.getCurrentTitle().prefixedText
if title == SSC_PAGE then return nil end
local query = mw.smw.ask{
'[[' .. title .. ']]',
'?DrlPageName#=DrlPageName',
limit = 1
}
if not query or not query[1] then return nil end
local id = firstValue(query[1].DrlPageName)
if type(id) ~= 'string' then return nil end
id = mw.text.trim(id)
if id:match('^RKTS[KT]%s') then return id end
return nil
end
-- Every quote segment naming this id, grouped into quotations.
local function fetchQuotations(id)
-- Every quote segment, filtered in Lua rather than by the query.
--
-- A wildcard on QuoteSource cannot do this correctly. "~*RKTSK 12*" also
-- matches RKTSK 127 and RKTSK 129, and anchoring on the closing parenthesis
-- to fix that then MISSES segments where the id is not last, because a
-- segment may name several sources at once:
--
-- Bodhisattvapratimokṣa… (RKTSK 248);Vinayaviniścayopāli… (RKTSK 68)
--
-- That combination silently halved the Vinayaviniścayopāliparipṛcchāsūtra
-- (13 quotations reported as 6) and zeroed the Pañcaviṃśatisāhasrikā.
-- Fetching the quote segments and matching "(id)" in Lua handles both.
local query = mw.smw.ask{
'[[' .. SSC_PREFIX .. ']][[QuoteSource::+]]',
'?QuoteSource#=QuoteSource',
'?#-=Page',
'?SegmentOrder#=SegmentOrder',
'?TranslationChapter#=TranslationChapter',
'?TranslationPageNumber#=TranslationPageNumber',
'?SourcePageNumber#=SourcePageNumber',
'?SegmentTranslation#=SegmentTranslation',
limit = SEGMENT_LIMIT
}
if not query then return {} end
local needle = '(' .. id .. ')'
local segments = {}
for _, row in ipairs(query) do
local order = tonumber(firstValue(row.SegmentOrder))
local source = firstValue(row.QuoteSource)
if order and type(source) == 'string'
and source:find(needle, 1, true) then
local text = firstValue(row.SegmentTranslation)
local words = 0
if type(text) == 'string' then
for _ in text:gmatch('%S+') do words = words + 1 end
end
table.insert(segments, {
order = order,
page = firstValue(row.Page),
chapter = firstValue(row.TranslationChapter),
folio = firstValue(row.SourcePageNumber),
pageNum = firstValue(row.TranslationPageNumber),
words = words,
})
end
end
table.sort(segments, function(a, b) return a.order < b.order end)
-- Collapse runs of consecutive SegmentOrder into one quotation, anchored to
-- the lowest order so a link lands at the start of the passage rather than
-- somewhere in its middle.
--
-- A chapter change breaks a run: two adjacent quotes from the same work in
-- different chapters of the SSC are two quotations. No run in the current
-- data actually crosses a chapter boundary, but the rule is correct and
-- costs nothing.
local quotations = {}
for _, seg in ipairs(segments) do
local last = quotations[#quotations]
if last and seg.order == last.lastOrder + 1 and seg.chapter == last.chapter then
last.lastOrder = seg.order
last.words = last.words + seg.words
else
table.insert(quotations, {
page = seg.page,
order = seg.order,
lastOrder = seg.order,
chapter = seg.chapter,
folio = seg.folio,
pageNum = seg.pageNum,
words = seg.words,
})
end
end
return quotations
end
-- Number of distinct quotations, for the button label and for deciding whether
-- to render anything at all.
-- The template asks for the count to decide whether to render, then for the
-- list. Cached so that is one query per page rather than three.
local cache = nil
local function quotations(frame)
if cache == nil then
local id = rktsId(frame)
cache = id and fetchQuotations(id) or {}
end
return cache
end
function p.count(frame)
local list = quotations(frame)
if #list == 0 then return '' end
return tostring(#list)
end
-- The list itself.
function p.list(frame)
local list = quotations(frame)
if #list == 0 then return '' end
local out = {'<div class="ssc-citations">'}
for i, q in ipairs(list) do
table.insert(out, '<div class="ssc-citation">')
table.insert(out, '<span class="ssc-citation-index">' .. i .. '</span>')
table.insert(out, '<span class="ssc-citation-body">')
if q.chapter and q.chapter ~= '' then
table.insert(out, '<span class="ssc-citation-chapter">Chapter ' ..
tostring(q.chapter) .. '</span>')
end
-- Folio first: SourcePageNumber is the Tibetan block-print folio ("114a"),
-- which is what a scholar cites. It can hold several values when a
-- passage spans folios, so only the first — the start of the quotation —
-- is shown. TranslationPageNumber is the printed English page, kept as a
-- fallback for the handful of segments with no folio.
if q.folio and q.folio ~= '' then
local folio = tostring(q.folio):gsub(';.*$', '')
table.insert(out, '<span class="ssc-citation-page">folio ' ..
folio .. '</span>')
elseif q.pageNum and q.pageNum ~= '' then
table.insert(out, '<span class="ssc-citation-page">p. ' ..
tostring(q.pageNum) .. '</span>')
end
if q.words > LONG_QUOTE_WORDS then
table.insert(out, '<span class="ssc-citation-long">long quotation</span>')
end
table.insert(out, '</span>')
-- Link into the bilingual layout rather than the bare TM segment page:
-- that is where the passage is readable side by side. The chapter is the
-- last path element, and the anchor is the segment number, which
-- Template:DisplaySegmentRow already emits as the row's id.
--
-- Falls back to the segment page when the quotation has no chapter,
-- since there would be nothing to build the path from.
if q.chapter and q.chapter ~= '' then
table.insert(out, '<span class="ssc-citation-link">[[' .. BILINGUAL_BASE ..
tostring(q.chapter) .. '#' .. tostring(q.order) ..
'|Read the passage]]</span>')
elseif q.page then
table.insert(out, '<span class="ssc-citation-link">[[' .. q.page ..
'|Read the passage]]</span>')
end
table.insert(out, '</div>')
end
table.insert(out, '</div>')
return frame:preprocess(table.concat(out))
end
return p