Module:SscCitedWorks: Difference between revisions
From Bodhicitta
(List of works quoted in the Śikṣāsamuccaya, counting quotations not segments) |
(Count ContainsQuote segments as well as Quote ones) |
||
| (7 intermediate revisions by the same user not shown) | |||
| Line 6: | Line 6: | ||
-- The join is the RKTS catalogue number. A quote segment records its source as | -- The join is the RKTS catalogue number. A quote segment records its source as | ||
-- "Ratnameghasūtra (RKTSK 231)", and the text page holds the same id in | -- "Ratnameghasūtra (RKTSK 231)", and the text page holds the same id in | ||
-- | -- the page itself, since QuoteSource is now a page property. | ||
-- | -- | ||
-- COUNTS ARE QUOTATIONS, NOT SEGMENTS. One quotation is often split across | -- COUNTS ARE QUOTATIONS, NOT SEGMENTS. One quotation is often split across | ||
| Line 27: | Line 27: | ||
end | end | ||
-- Quotation counts keyed by | -- Quotation counts keyed by the cited page. | ||
local function | -- | ||
-- Selected on SegmentFormat, NOT on [[QuoteSource::+]]. | |||
-- | |||
-- The wildcard on QuoteSource under-reports here: a segment whose value was | |||
-- retargeted by a new redirect stays invisible to it until something re-stores | |||
-- that segment. Measured after a curator added redirects in Sep 2026, it saw | |||
-- 594 of the SSC's 633 quote segments and this list silently lost 13 works. | |||
-- | |||
-- SegmentFormat is a plain text value the wildcard handles correctly, so the | |||
-- segment set is taken from it and QuoteSource is read from the printout — | |||
-- segments with no source are skipped in the loop below. | |||
local function quotationsByPage() | |||
local query = mw.smw.ask{ | local query = mw.smw.ask{ | ||
'[[' .. SSC_PREFIX .. ']][[ | -- Quote segments AND commentary segments that embed one (ContainsQuote). | ||
'?QuoteSource#=QuoteSource', | -- Both carry a QuoteSource, and the panel on the cited work shows both, so | ||
-- this list must count both or the two pages disagree. | |||
'[[' .. SSC_PREFIX .. ']][[SegmentFormat::Quote||Verse inside a quote]] OR [[' .. | |||
SSC_PREFIX .. ']][[ContainsQuote::true]]', | |||
-- '#-' gives the bare page name: plain ?QuoteSource returns the display | |||
-- title (no 'Texts/'), and '#' inserts a space after the namespace. | |||
'?QuoteSource#-=QuoteSource', | |||
'?SegmentOrder#=SegmentOrder', | '?SegmentOrder#=SegmentOrder', | ||
'?TranslationChapter#=TranslationChapter', | '?TranslationChapter#=TranslationChapter', | ||
| Line 38: | Line 55: | ||
if not query then return {} end | if not query then return {} end | ||
local | local bySource = {} | ||
for _, row in ipairs(query) do | for _, row in ipairs(query) do | ||
local order = tonumber(firstValue(row.SegmentOrder)) | local order = tonumber(firstValue(row.SegmentOrder)) | ||
local | if order then | ||
local chapter = tostring(firstValue(row.TranslationChapter) or '') | |||
-- A segment can name SEVERAL works when a passage is shared between | |||
-- them, so every value counts towards its own work. Taking only the | |||
source | -- first credited the whole passage to one and left the other empty. | ||
local sources = row.QuoteSource | |||
if type(sources) ~= 'table' then sources = { sources } end | |||
for _, source in ipairs(sources) do | |||
-- The printout carries the page the value resolved to, redirects | |||
-- followed, so counts key on the work itself rather than on | |||
-- whichever alias a segment happened to use. The Samādhirājasūtra | |||
-- is cited under five names and must count as one work. | |||
local page = source | |||
if type(page) == 'table' then page = page.fulltext end | |||
if type(page) == 'string' then page = mw.text.trim(page) end | |||
if type(page) == 'string' and page ~= '' then | |||
bySource[page] = bySource[page] or {} | |||
table.insert(bySource[page], { order = order, chapter = chapter }) | |||
end | |||
end | |||
end | end | ||
end | end | ||
-- Runs are found PER SOURCE, not by walking the whole sequence. Two different | -- Runs are found PER SOURCE, not by walking the whole sequence. Two different | ||
-- works quoted in adjacent segments are two quotations, and walking the | -- works quoted in adjacent segments are two quotations, and walking the | ||
-- combined list makes each | -- combined list makes each look like it interrupts the other. Grouping within | ||
-- | -- a source also matches Module:SscCitations, whose per-page counts are what a | ||
-- | -- reader sees on the text page itself — the two must agree. | ||
local counts = {} | local counts = {} | ||
for | for page, list in pairs(bySource) do | ||
table.sort(list, function(a, b) return a.order < b.order end) | table.sort(list, function(a, b) return a.order < b.order end) | ||
local n, previous = 0, nil | local n, previous = 0, nil | ||
| Line 82: | Line 97: | ||
previous = seg | previous = seg | ||
end | end | ||
counts[ | counts[page] = n | ||
end | end | ||
return counts | return counts | ||
| Line 88: | Line 103: | ||
function p.main(frame) | function p.main(frame) | ||
local counts = | local counts = quotationsByPage() | ||
local | local names = {} | ||
for | for page in pairs(counts) do table.insert(names, page) end | ||
if # | if #names == 0 then return '' end | ||
table.sort( | table.sort(names) | ||
-- One query for every | -- One query for every cited page, rather than one per work. Values repeat | ||
-- the page name only, per SMW's OR syntax: [[P::a||b||c]]. Repeating the | |||
-- property ([[P::a||P::b]]) silently returns almost nothing. | |||
local pages = mw.smw.ask{ | local pages = mw.smw.ask{ | ||
'[[ | '[[' .. table.concat(names, '||') .. ']]', | ||
'?#-=Page', | '?#-=Page', | ||
'?Display title of=Title', | '?Display title of=Title', | ||
'?FullTitleTib=FullTitleTib', | '?FullTitleTib=FullTitleTib', | ||
| Line 111: | Line 127: | ||
for _, row in ipairs(pages) do | for _, row in ipairs(pages) do | ||
local page = firstValue(row.Page) | local page = firstValue(row.Page) | ||
local | -- counts is keyed by the resolved page (see quotationsByPage), which is | ||
if page and page ~= SSC_PAGE and | -- what this query returns, so the two line up directly. | ||
local n = page and counts[page] | |||
if page and page ~= SSC_PAGE and n then | |||
local label = firstValue(row.Title) | local label = firstValue(row.Title) | ||
if not label or label == '' then label = page:gsub('^Texts/', '') end | if not label or label == '' then label = page:gsub('^Texts/', '') end | ||
| Line 121: | Line 139: | ||
trans = firstValue(row.FullTitleTrans), | trans = firstValue(row.FullTitleTrans), | ||
class = firstValue(row.Classification), | class = firstValue(row.Classification), | ||
count = | count = n, | ||
}) | }) | ||
end | end | ||
| Line 138: | Line 156: | ||
table.insert(out, '<div class="row filterable">') | table.insert(out, '<div class="row filterable">') | ||
-- The vajra sits ON the border between the two columns (right: -26px on a | |||
-- position-relative parent), tying them together. Desktop only. | |||
table.insert(out, '<div class="col-md-3 col-lg-2 pr-md-5 pt-md-1 pb-1 text-center ' .. | table.insert(out, '<div class="col-md-3 col-lg-2 pr-md-5 pt-md-1 pb-1 text-center ' .. | ||
'text-md-right border-right timeline-border font-serif position-relative">' .. | 'text-md-right border-right timeline-border font-serif position-relative">' .. | ||
'<div class="ssc-cited-vajra d-none d-md-block position-absolute">' .. | |||
'[[File:Red Horizontal Vajra.png|50px|link=|]]</div>' .. | |||
'<span class="ssc-cited-count">' .. w.count .. '</span>' .. | '<span class="ssc-cited-count">' .. w.count .. '</span>' .. | ||
'<span class="ssc-cited-label">' .. | '<span class="ssc-cited-label">' .. | ||
Latest revision as of 14:03, 22 September 2026
Documentation for this module may be created at Module:SscCitedWorks/doc
local p = {}
-- The list of works quoted in the Śikṣāsamuccaya, generated from the aligned
-- translation memory.
--
-- The join is the RKTS catalogue number. A quote segment records its source as
-- "Ratnameghasūtra (RKTSK 231)", and the text page holds the same id in
-- the page itself, since QuoteSource is now a page property.
--
-- COUNTS ARE QUOTATIONS, NOT SEGMENTS. One quotation is often split across
-- consecutive segments, so counting segments overstates badly: the
-- Pitāputrasamāgamanasūtra has 62 quote segments but is quoted 14 times, and one
-- of its runs is 36 segments long. Across the SSC, 633 segments are 345
-- quotations. SMW cannot express "consecutive", so the grouping happens here.
--
-- A chapter change breaks a run: two adjacent quotes from one work in different
-- chapters are two quotations. No run in the current data crosses a chapter
-- boundary, but the rule is correct and costs nothing.
local SSC_PREFIX = '~Translation Memory:016-Tsadra-SSC-Root/*'
local SSC_PAGE = 'Texts/Śikṣāsamuccaya'
local SEGMENT_LIMIT = 5000
local function firstValue(v)
if type(v) == 'table' then return v[1] end
return v
end
-- Quotation counts keyed by the cited page.
--
-- Selected on SegmentFormat, NOT on [[QuoteSource::+]].
--
-- The wildcard on QuoteSource under-reports here: a segment whose value was
-- retargeted by a new redirect stays invisible to it until something re-stores
-- that segment. Measured after a curator added redirects in Sep 2026, it saw
-- 594 of the SSC's 633 quote segments and this list silently lost 13 works.
--
-- SegmentFormat is a plain text value the wildcard handles correctly, so the
-- segment set is taken from it and QuoteSource is read from the printout —
-- segments with no source are skipped in the loop below.
local function quotationsByPage()
local query = mw.smw.ask{
-- Quote segments AND commentary segments that embed one (ContainsQuote).
-- Both carry a QuoteSource, and the panel on the cited work shows both, so
-- this list must count both or the two pages disagree.
'[[' .. SSC_PREFIX .. ']][[SegmentFormat::Quote||Verse inside a quote]] OR [[' ..
SSC_PREFIX .. ']][[ContainsQuote::true]]',
-- '#-' gives the bare page name: plain ?QuoteSource returns the display
-- title (no 'Texts/'), and '#' inserts a space after the namespace.
'?QuoteSource#-=QuoteSource',
'?SegmentOrder#=SegmentOrder',
'?TranslationChapter#=TranslationChapter',
limit = SEGMENT_LIMIT
}
if not query then return {} end
local bySource = {}
for _, row in ipairs(query) do
local order = tonumber(firstValue(row.SegmentOrder))
if order then
local chapter = tostring(firstValue(row.TranslationChapter) or '')
-- A segment can name SEVERAL works when a passage is shared between
-- them, so every value counts towards its own work. Taking only the
-- first credited the whole passage to one and left the other empty.
local sources = row.QuoteSource
if type(sources) ~= 'table' then sources = { sources } end
for _, source in ipairs(sources) do
-- The printout carries the page the value resolved to, redirects
-- followed, so counts key on the work itself rather than on
-- whichever alias a segment happened to use. The Samādhirājasūtra
-- is cited under five names and must count as one work.
local page = source
if type(page) == 'table' then page = page.fulltext end
if type(page) == 'string' then page = mw.text.trim(page) end
if type(page) == 'string' and page ~= '' then
bySource[page] = bySource[page] or {}
table.insert(bySource[page], { order = order, chapter = chapter })
end
end
end
end
-- Runs are found PER SOURCE, not by walking the whole sequence. Two different
-- works quoted in adjacent segments are two quotations, and walking the
-- combined list makes each look like it interrupts the other. Grouping within
-- a source also matches Module:SscCitations, whose per-page counts are what a
-- reader sees on the text page itself — the two must agree.
local counts = {}
for page, list in pairs(bySource) do
table.sort(list, function(a, b) return a.order < b.order end)
local n, previous = 0, nil
for _, seg in ipairs(list) do
local continues = previous
and seg.chapter == previous.chapter
and seg.order == previous.order + 1
if not continues then n = n + 1 end
previous = seg
end
counts[page] = n
end
return counts
end
function p.main(frame)
local counts = quotationsByPage()
local names = {}
for page in pairs(counts) do table.insert(names, page) end
if #names == 0 then return '' end
table.sort(names)
-- One query for every cited page, rather than one per work. Values repeat
-- the page name only, per SMW's OR syntax: [[P::a||b||c]]. Repeating the
-- property ([[P::a||P::b]]) silently returns almost nothing.
local pages = mw.smw.ask{
'[[' .. table.concat(names, '||') .. ']]',
'?#-=Page',
'?Display title of=Title',
'?FullTitleTib=FullTitleTib',
'?Fulltitletrans=FullTitleTrans',
'?Classification=Classification',
limit = 500
}
if not pages then return '' end
local works = {}
for _, row in ipairs(pages) do
local page = firstValue(row.Page)
-- counts is keyed by the resolved page (see quotationsByPage), which is
-- what this query returns, so the two line up directly.
local n = page and counts[page]
if page and page ~= SSC_PAGE and n then
local label = firstValue(row.Title)
if not label or label == '' then label = page:gsub('^Texts/', '') end
table.insert(works, {
page = page,
label = label,
tib = firstValue(row.FullTitleTib),
trans = firstValue(row.FullTitleTrans),
class = firstValue(row.Classification),
count = n,
})
end
end
if #works == 0 then return '' end
-- Alphabetical by title, matching the hand-written page it replaces. Sorting
-- by count would bury the sūtras quoted once among the busiest works.
table.sort(works, function(a, b) return a.label < b.label end)
local out = {}
for _, w in ipairs(works) do
local author = {}
if w.tib and w.tib ~= '' then table.insert(author, 'Tib. ' .. w.tib) end
if w.trans and w.trans ~= '' then table.insert(author, 'Eng. ' .. w.trans) end
table.insert(out, '<div class="row filterable">')
-- The vajra sits ON the border between the two columns (right: -26px on a
-- position-relative parent), tying them together. Desktop only.
table.insert(out, '<div class="col-md-3 col-lg-2 pr-md-5 pt-md-1 pb-1 text-center ' ..
'text-md-right border-right timeline-border font-serif position-relative">' ..
'<div class="ssc-cited-vajra d-none d-md-block position-absolute">' ..
'[[File:Red Horizontal Vajra.png|50px|link=|]]</div>' ..
'<span class="ssc-cited-count">' .. w.count .. '</span>' ..
'<span class="ssc-cited-label">' ..
(w.count == 1 and 'quotation' or 'quotations') .. '</span></div>')
table.insert(out, frame:expandTemplate{
title = 'TimelineTileHorizontalTranslations',
args = {
wrapperclasses = 'col-md-9 px-2 px-md-5 py-0 font-serif',
texts = '[[' .. w.page .. '|' .. w.label .. ']]',
author = table.concat(author, ' · '),
description = w.class or '',
}
})
table.insert(out, '</div>')
end
return frame:preprocess(table.concat(out, '\n'))
end
return p