Module:Soldier: Difference between revisions
Class links go to Category:Class N (merged roster + dates) |
Infobox: drop certificate/arrive/depart/years; expand other_class codes |
||
| (7 intermediate revisions by the same user not shown) | |||
| Line 711: | Line 711: | ||
end | end | ||
local function | -- Roster "other_class" codes → readable course names (By The Numbers / MITC charts). | ||
return | local function expandOtherClassToken(tok) | ||
tok = tidyDisplay(tok) | |||
if tok == '' then | |||
return '' | |||
end | |||
local u = mw.ustring.upper(tok) | |||
u = mw.ustring.gsub(u, '%s+', ' ') | |||
u = trim(u) | |||
local | local n = mw.ustring.match(u, '^SC%s*B%s*(%d+)$') | ||
if n then | |||
if | return 'Special Course B ' .. n | ||
return | end | ||
n = mw.ustring.match(u, '^SC%s*(%d+)$') | |||
if n then | |||
-- SC# aligns with AGF Photo Interpretation / “Special Course” numbering | |||
-- (e.g. SC8 = Special Course 8 in PI). | |||
return 'Special Course ' .. n | |||
end | |||
n = mw.ustring.match(u, '^DC%s*(%d+)$') | |||
if n then | |||
return 'Division Class ' .. n | |||
end | |||
n = mw.ustring.match(u, '^MITU%s*(%d+)$') | |||
if n then | |||
return 'Mobile Intelligence Training Unit ' .. n | |||
end | |||
n = mw.ustring.match(u, '^RDC%s*(%d+)$') | |||
if n then | |||
return 'Redeployment Class ' .. n | |||
end | |||
n = mw.ustring.match(u, '^RCM%s*(%d+)$') | |||
if n then | |||
return 'Radio Countermeasures Class ' .. n | |||
end | |||
n = mw.ustring.match(u, '^OB%s*(%d+)$') | |||
if n then | |||
return 'Order of Battle Class ' .. n | |||
end | |||
if u == 'SIG' or u == 'SIG-SIG' or mw.ustring.match(u, '^SIG[%s%-]') then | |||
return 'Special Intelligence Group–Signal' | |||
end | end | ||
if mw.ustring. | local unitNum, unitKind = mw.ustring.match(u, '^(%d+)(?:ST|ND|RD|TH)%s+(.+)$') | ||
if unitNum and unitKind then | |||
local kind = trim(unitKind) | |||
if kind == 'INF' then | |||
kind = 'Infantry' | |||
elseif kind == 'ARMD' then | |||
kind = 'Armored' | |||
else | |||
kind = prettyProse(kind) | |||
end | |||
local ord = unitNum | |||
local last = mw.ustring.sub(unitNum, -1) | |||
local last2 = mw.ustring.len(unitNum) >= 2 and mw.ustring.sub(unitNum, -2) or '' | |||
if last2 ~= '11' and last2 ~= '12' and last2 ~= '13' then | |||
if last == '1' then | |||
ord = unitNum .. 'st' | |||
elseif last == '2' then | |||
ord = unitNum .. 'nd' | |||
elseif last == '3' then | |||
ord = unitNum .. 'rd' | |||
else | |||
ord = unitNum .. 'th' | |||
end | |||
else | |||
ord = unitNum .. 'th' | |||
end | end | ||
return ord .. ' ' .. kind | |||
end | end | ||
return | |||
return prettyProse(tok) | |||
end | end | ||
local function normalizeNoteKey(s) | local function expandOtherClass(raw) | ||
raw = tidyDisplay(raw) | |||
if raw == '' then | |||
return '' | |||
end | |||
local parts = {} | |||
for piece in mw.ustring.gmatch(raw, '[^;,]+') do | |||
local expanded = expandOtherClassToken(piece) | |||
if expanded ~= '' then | |||
table.insert(parts, expanded) | |||
end | |||
end | |||
return table.concat(parts, '; ') | |||
end | |||
local function prettyNamePart(s) | |||
return prettyNamePhrase(s, false) | |||
end | |||
local function isNegativeClause(s) | |||
local lower = mw.ustring.lower(s) | |||
if lower == 'failed' or mw.ustring.find(lower, '^failed%s') or mw.ustring.find(lower, '%sfailed%s') or mw.ustring.find(lower, '%sfailed$') then | |||
return true | |||
end | |||
-- Bare class-chart non-graduate marker (e.g. "NG Class 10 OB", trailing "NG") | |||
if lower == 'ng' or mw.ustring.find(lower, '^ng%s') or mw.ustring.find(lower, '%sng%s') or mw.ustring.find(lower, '%sng$') or mw.ustring.find(lower, 'nongrad') then | |||
return true | |||
end | |||
for _, phrase in ipairs(NEGATIVE_PHRASES) do | |||
if mw.ustring.find(lower, phrase, 1, true) then | |||
return true | |||
end | |||
end | |||
return false | |||
end | |||
local function normalizeNoteKey(s) | |||
s = mw.ustring.lower(trim(tidyDisplay(s))) | s = mw.ustring.lower(trim(tidyDisplay(s))) | ||
s = mw.ustring.gsub(s, '^also%s+', '') | s = mw.ustring.gsub(s, '^also%s+', '') | ||
| Line 1,857: | Line 1,952: | ||
local nativity = expandNativity(parentArg(frame, 'nativity')) | local nativity = expandNativity(parentArg(frame, 'nativity')) | ||
local religion = expandReligion(parentArg(frame, 'religion')) | local religion = expandReligion(parentArg(frame, 'religion')) | ||
-- education / marital / occupation stay in Technical record (NARA snapshot); | |||
-- curated life facts belong in Biography / Timeline, not the infobox. | |||
local knownFor = prettyProse(parentArg(frame, 'known_for')) | local knownFor = prettyProse(parentArg(frame, 'known_for')) | ||
local awards = prettyProse(parentArg(frame, 'awards')) | local awards = prettyProse(parentArg(frame, 'awards')) | ||
| Line 1,868: | Line 1,962: | ||
awards = trim(first or awards) | awards = trim(first or awards) | ||
end | end | ||
local function datePlaceCell(date, place, year) | local function datePlaceCell(date, place, year) | ||
| Line 1,887: | Line 1,980: | ||
if alias ~= '' or birth ~= '' or death ~= '' or nativity ~= '' | if alias ~= '' or birth ~= '' or death ~= '' or nativity ~= '' | ||
or religion | or religion ~= '' or knownFor ~= '' or awards ~= '' then | ||
addHeader(tbl, 'Personal details') | addHeader(tbl, 'Personal details') | ||
end | end | ||
| Line 1,897: | Line 1,989: | ||
addRow(tbl, 'Nativity', nativity) | addRow(tbl, 'Nativity', nativity) | ||
addRow(tbl, 'Religion', religion) | addRow(tbl, 'Religion', religion) | ||
addRow(tbl, 'Known for', knownFor) | addRow(tbl, 'Known for', knownFor) | ||
addRow(tbl, 'Awards', awards) | addRow(tbl, 'Awards', awards) | ||
addHeader(tbl, 'Military service') | addHeader(tbl, 'Military service') | ||
| Line 1,921: | Line 2,007: | ||
addRow(tbl, 'Arm / service', prettyProse(armName)) | addRow(tbl, 'Arm / service', prettyProse(armName)) | ||
end | end | ||
-- years_of_service / date_arrive / date_depart / certificate stay in Technical | |||
-- record only (rarely curated; certificate is a cryptic chart number). | |||
addRow(tbl, 'ASN', parentArg(frame, 'asn'), 'serial') | addRow(tbl, 'ASN', parentArg(frame, 'asn'), 'serial') | ||
| Line 1,942: | Line 2,029: | ||
addRow(tbl, 'Camp Ritchie', 'Class ' .. table.concat(classBits, ', ')) | addRow(tbl, 'Camp Ritchie', 'Class ' .. table.concat(classBits, ', ')) | ||
end | end | ||
addRow(tbl, 'Section', parentArg(frame, 'class_section')) | addRow(tbl, 'Section', mw.ustring.upper(parentArg(frame, 'class_section'))) | ||
addRow(tbl, 'Other class', parentArg(frame, 'other_class')) | addRow(tbl, 'Other class', expandOtherClass(parentArg(frame, 'other_class'))) | ||
local enlist = prettyPlace(parentArg(frame, 'enlistment_place')) | local enlist = prettyPlace(parentArg(frame, 'enlistment_place')) | ||
| Line 1,953: | Line 2,040: | ||
addRow(tbl, 'Battles / wars', formatBattles(parentArg(frame, 'battles'))) | addRow(tbl, 'Battles / wars', formatBattles(parentArg(frame, 'battles'))) | ||
addTeamsInfoboxRow(tbl, frame) | addTeamsInfoboxRow(tbl, frame) | ||
-- Awards ribbons render in the article body (p.awardsSection), not this | -- Awards ribbons render in the article body (p.awardsSection), not this | ||
| Line 2,441: | Line 2,525: | ||
end | end | ||
-- related-rail: | -- related-rail: live team rosters via category members (filled by Common.js) | ||
function p.relatedSoldiers(frame) | function p.relatedSoldiers(frame) | ||
local pageTitle = mw.title.getCurrentTitle().prefixedText | local pageTitle = mw.title.getCurrentTitle().prefixedText | ||
-- Same sources as the infobox Teams row: roster IPW, |teams=, research notes. | -- Same sources as the infobox Teams row: roster IPW, |teams=, research notes. | ||
| Line 2,510: | Line 2,541: | ||
addTeams(namesFromTeamsParam(parentArg(frame, 'teams'))) | addTeams(namesFromTeamsParam(parentArg(frame, 'teams'))) | ||
addTeams(noteTeamNames(pageResearchNotesText())) | addTeams(noteTeamNames(pageResearchNotesText())) | ||
if #teamNames == 0 then | |||
return '' | |||
end | |||
local wrap = mw.html.create('div') | |||
:addClass('soldier-related') | |||
:attr('data-current-title', pageTitle) | |||
wrap:tag('div'):addClass('soldier-related-heading'):wikitext('Teammates') | |||
for _, teamName in ipairs(teamNames) do | for _, teamName in ipairs(teamNames) do | ||
local cat = 'Category:Team ' .. teamName | |||
local block = wrap:tag('div') | |||
:addClass('soldier-related-block') | |||
:attr('data-team-cat', cat) | |||
block:tag('div') | |||
:addClass('soldier-related-label') | |||
:wikitext('Team — ' .. teamNameLink(teamName)) | |||
-- Noscript / pre-JS fallback: category link. Common.js replaces with live names. | |||
block:tag('div') | |||
:addClass('soldier-related-links') | |||
:addClass('soldier-related-links-live') | |||
:wikitext('[[' .. cat .. '|Browse team roster]]') | |||
end | end | ||
wrap:tag('div') | |||
:addClass('soldier-related-note') | |||
:wikitext( | |||
'These are other soldiers listed on the same team roster. ' | |||
.. 'People moved between teams; overlapping service dates are not always documented.' | |||
) | |||
return tostring(wrap) | |||
end | end | ||
| Line 2,548: | Line 2,597: | ||
return '' | return '' | ||
end | end | ||
-- Prefer interviews; accept legacy `items` key from hand-edited cues. | |||
local interviews = rec.interviews | local interviews = rec.interviews | ||
if type(interviews) ~= 'table' then | |||
interviews = rec.items | |||
end | |||
local films = rec.films | local films = rec.films | ||
local nInterviews = seqCount(interviews) | local nInterviews = seqCount(interviews) | ||
| Line 2,559: | Line 2,612: | ||
if nInterviews > 0 then | if nInterviews > 0 then | ||
local links = {} | local links = {} | ||
local anyAudio = false | |||
for _, item in ipairs(interviews) do | for _, item in ipairs(interviews) do | ||
local heading = item.heading or 'Interview' | local heading = item.heading or 'Interview' | ||
local short = item.short or heading | local short = item.short or heading | ||
if item.kind == 'audio' then | |||
anyAudio = true | |||
end | |||
table.insert( | table.insert( | ||
links, | links, | ||
| Line 2,568: | Line 2,625: | ||
end | end | ||
local label = nInterviews == 1 and 'Interview' or 'Interviews' | local label = nInterviews == 1 and 'Interview' or 'Interviews' | ||
local verb = 'Watch' | |||
if nInterviews == 1 and interviews[1].kind == 'audio' then | |||
verb = 'Listen to' | |||
elseif anyAudio and nInterviews > 1 then | |||
verb = 'Hear' | |||
end | |||
local body | local body | ||
if nInterviews == 1 then | if nInterviews == 1 then | ||
body = string.format( | body = string.format( | ||
' | '%s %s on [[Ritchie Boy Interviews#%s|%s]].', | ||
verb, | |||
name, | name, | ||
interviews[1].heading, | interviews[1].heading, | ||
| Line 2,578: | Line 2,642: | ||
else | else | ||
body = string.format( | body = string.format( | ||
' | '%s %s in %d recordings on [[Ritchie Boy Interviews]]: %s.', | ||
verb, | |||
name, | name, | ||
nInterviews, | nInterviews, | ||