편집 요약 없음 |
편집 요약 없음 |
||
| (같은 사용자의 중간 판 9개는 보이지 않습니다) | |||
| 9번째 줄: | 9번째 줄: | ||
end | end | ||
-- | -- 안전한 HTML 인코딩 (데이터 출력 시 특수 문자 오류 방지) | ||
local function safe_html_encode(s) | |||
if not s or s == '' then return '' end | |||
-- mw.text.html_encode 사용 시 오류 발생 가능성이 낮으므로 우선 사용 | |||
if mw.text and mw.text.html_encode then | |||
return mw.text.html_encode(tostring(s)) | |||
end | |||
-- 대체 인코딩 (mw.text 라이브러리 자체가 불안정할 경우 대비) | |||
s = tostring(s) | |||
s = s:gsub('&', '&') | |||
s = s:gsub('<', '<') | |||
s = s:gsub('>', '>') | |||
return s | |||
end | |||
-- 시그니처 기술 수집 및 HTML 리스트 반환 | |||
local function gather_techs(args) | local function gather_techs(args) | ||
local techs = {} | local techs = {} | ||
for i = 1, 10 do | for i = 1, 10 do | ||
local k = '시그니처기술' .. i | local k = '시그니처기술' .. i | ||
local val = trim(args[k]) | |||
table.insert(techs, | if val ~= '' then | ||
table.insert(techs, val) | |||
end | end | ||
end | end | ||
return | if #techs > 0 then | ||
local tstr = '<ul style="list-style:none; margin:0; padding:0 0 0 10px; font-size:95%;">' | |||
for i, tname in ipairs(techs) do | |||
local marker = '①' | |||
if i == 2 then marker = '②' end | |||
if i == 3 then marker = '③' end | |||
if i > 3 then marker = '•' end | |||
tstr = tstr .. string.format('<li style="margin-top:2px;">%s %s</li>', marker, safe_html_encode(tname)) | |||
end | |||
tstr = tstr .. '</ul>' | |||
return tstr | |||
end | |||
return '' | |||
end | end | ||
-- 이미지 블록 ( | -- 이미지 블록 (위키텍스트 반환) | ||
local function build_image_block(args) | local function build_image_block(args) | ||
local | local main_file = trim(args['이미지_VN'] or args['이미지_애니'] or 'NoImage.png') | ||
local output = {} | |||
table.insert(output, string.format('[[파일:%s|250px|center]]', mw.text.encode(main_file))) | |||
table.insert(output, '<div style="text-align:center; font-size:85%; margin-top:5px;">(애니 / VN 스위치)</div>') | |||
return table.concat(output, '\n') | |||
end | end | ||
-- 이름 블록 | -- 이름 블록 (HTML 반환) | ||
local function build_name_block(args) | local function build_name_block(args) | ||
local kr = trim(args['이름'] or '') | local kr = trim(args['이름'] or '') | ||
| 63번째 줄: | 66번째 줄: | ||
local en = trim(args['이름_영어'] or '') | local en = trim(args['이름_영어'] or '') | ||
local parts = {} | local parts = {} | ||
if kr ~= '' then table.insert(parts, string.format('< | |||
if kr ~= '' then | |||
table.insert(parts, string.format('<strong style="font-size:1.1em;">이름: %s</strong>', safe_html_encode(kr))) | |||
end | |||
local sub_parts = {} | |||
if jp ~= '' then table.insert(sub_parts, safe_html_encode(jp)) end | |||
if en ~= '' then table.insert(sub_parts, safe_html_encode(en)) end | |||
table.insert(parts, string.format('<div | |||
if #sub_parts > 0 then | |||
table.insert(parts, string.format('<div style="font-size:90%%; margin-top:2px;">%s</div>', table.concat(sub_parts, ' / '))) | |||
end | end | ||
return table.concat(parts, '\n') | return table.concat(parts, '\n') | ||
| 77번째 줄: | 82번째 줄: | ||
-- 라벨+값 행 생성 (값 없으면 빈 문자열) | -- 라벨+값 행 생성 (값 없으면 빈 문자열) | ||
local function | local function info_row(label, value) | ||
if | -- value가 nil이거나 빈 문자열이면 출력하지 않음 | ||
return string.format('<div | local val = trim(value) | ||
if val == '' then return '' end | |||
-- 목록 형태로 반환 ('- 라벨: 값' 형태) | |||
return string.format('<div style="margin: 3px 0; padding-left: 10px;">- <strong>%s</strong>: %s</div>', safe_html_encode(label), safe_html_encode(val)) | |||
end | end | ||
-- 나이 출력 규칙 | -- 나이 출력 규칙 | ||
local function build_age_row(args) | local function build_age_row(args) | ||
local nv = trim(args['나이_VN']) | local nv = trim(args['나이_VN']) | ||
local na = trim(args['나이_애니']) | local na = trim(args['나이_애니']) | ||
local age_str = '' | |||
if nv ~= '' and na ~= '' then | if nv ~= '' and na ~= '' then | ||
age_str = string.format('VN %s / Anime %s', safe_html_encode(nv), safe_html_encode(na)) | |||
elseif nv ~= '' then | elseif nv ~= '' then | ||
age_str = string.format('VN %s', safe_html_encode(nv)) | |||
elseif na ~= '' then | elseif na ~= '' then | ||
age_str = string.format('Anime %s', safe_html_encode(na)) | |||
else | else | ||
return '' | return '' | ||
end | end | ||
-- 나이 필드만 예외적으로 info_row 대신 직접 출력 | |||
if age_str ~= '' then | |||
return string.format('<div style="margin: 3px 0; padding-left: 10px;">- <strong>나이</strong>: %s</div>', age_str) | |||
end | |||
return '' | |||
end | end | ||
function p.main(frame) | function p.main(frame) | ||
local parent = frame:getParent() or frame | local parent = frame:getParent() or frame | ||
local args = parent.args or {} | local args = parent.args or {} | ||
local | -- 모든 인수를 문자열로 트림 (nil 방지) | ||
local clean_args = {} | |||
for k, v in pairs(args) do | |||
clean_args[k] = trim(v) | |||
end | |||
local theme = clean_args['색상'] ~= '' and clean_args['색상'] or '#007bff' | |||
local image_block = build_image_block(clean_args) | |||
local | local name_block = build_name_block(clean_args) | ||
-- | -- 섹션별 정보 구성 | ||
local | local basic = table.concat({ | ||
info_row('성별', clean_args['성별']), | |||
fc = | build_age_row(clean_args), | ||
info_row('생일', clean_args['생일']), | |||
if | info_row('신장', clean_args['신장']), | ||
info_row('체중', clean_args['체중']), | |||
info_row('머리색', clean_args['머리색']), | |||
info_row('눈색', clean_args['눈색']), | |||
}, '\n') | |||
local fc_techs = gather_techs(clean_args) | |||
local fc = table.concat({ | |||
info_row('포지션', clean_args['포지션']), | |||
}, '\n') | |||
if fc_techs ~= '' then | |||
fc = fc .. '<div style="margin: 3px 0; padding-left: 10px;">- <strong>시그니처 기술</strong>:</div>' .. fc_techs | |||
end | end | ||
local personal = table.concat({ | |||
info_row('학교', clean_args['학교']), | |||
info_row('거주지', clean_args['거주지']), | |||
info_row('취미', clean_args['취미']), | |||
info_row('좋아하는 음식', clean_args['좋아하는음식']), | |||
}, '\n') | |||
local voice = table.concat({ | |||
local | info_row('일본어', clean_args['성우_JP']), | ||
info_row('영어', clean_args['성우_EN']), | |||
}, '\n') | |||
local debut = table.concat({ | |||
local | info_row('애니메이션', clean_args['애니_데뷔']), | ||
info_row('게임(VN)', clean_args['게임_데뷔']), | |||
info_row('만화', clean_args['만화_데뷔']), | |||
}, '\n') | |||
-- ********** 위키 테이블 문자열 조립 ********** | |||
local wiki_table = {} | |||
-- 1. 전체 컨테이너 테이블 시작 (2열: 이미지 / 정보) | |||
table.insert(wiki_table, string.format('{| class="wikitable" style="width: 100%%; max-width: 650px; border: 2px solid %s; border-collapse: collapse; margin: 1em auto; font-size: 95%%;"', safe_html_encode(theme))) | |||
table.insert(wiki_table, '|-') | |||
-- | -- 2. 좌측 열 (이미지 영역) | ||
table.insert(wiki_table, string.format('| style="width: 35%%; vertical-align: top; padding: 10px; background-color: #f8f8f8;" | %s', image_block)) | |||
table.insert( | |||
-- 우측 정보 | -- 3. 우측 열 (정보 영역) | ||
table.insert( | table.insert(wiki_table, '| style="width: 65%%; vertical-align: top; padding: 10px;" |') | ||
-- 이름 | -- 우측 영역 내부 내용 (이름, 섹션 헤더, 정보 리스트) | ||
table.insert( | local right_content = {} | ||
table.insert(right_content, name_block) | |||
table.insert(right_content, '<hr style="border: 0; border-top: 1px solid #ccc; margin: 8px 0;">') | |||
local function add_section(title, content) | local function add_section(title, content) | ||
if content ~= '' then | if trim(content) ~= '' then | ||
table.insert( | table.insert(right_content, string.format('<h4 style="color:%s; margin:8px 0 4px 0; padding:0; font-size:1em; border-bottom:1px solid %s;"><%s></h4>', safe_html_encode(theme), safe_html_encode(theme), safe_html_encode(title))) | ||
table.insert(right_content, content) | |||
end | end | ||
end | end | ||
| 177번째 줄: | 198번째 줄: | ||
add_section('성우', voice) | add_section('성우', voice) | ||
add_section('데뷔', debut) | add_section('데뷔', debut) | ||
table.insert(wiki_table, table.concat(right_content, '\n')) | |||
-- | -- 4. 테이블 닫기 | ||
return | table.insert(wiki_table, '|}') | ||
return table.concat(wiki_table, '\n') | |||
end | end | ||
return | return p | ||
2025년 12월 5일 (금) 03:27 기준 최신판
이 모듈에 대한 설명문서는 모듈:푸른 저편의 포리듬/ 캐릭터 프로필/설명문서에서 만들 수 있습니다
local p = {}
local mw = mw
local tt = mw.text
-- 안전 문자열 트림
local function trim(s)
if not s then return '' end
return tt.trim(tostring(s))
end
-- 안전한 HTML 인코딩 (데이터 출력 시 특수 문자 오류 방지)
local function safe_html_encode(s)
if not s or s == '' then return '' end
-- mw.text.html_encode 사용 시 오류 발생 가능성이 낮으므로 우선 사용
if mw.text and mw.text.html_encode then
return mw.text.html_encode(tostring(s))
end
-- 대체 인코딩 (mw.text 라이브러리 자체가 불안정할 경우 대비)
s = tostring(s)
s = s:gsub('&', '&')
s = s:gsub('<', '<')
s = s:gsub('>', '>')
return s
end
-- 시그니처 기술 수집 및 HTML 리스트 반환
local function gather_techs(args)
local techs = {}
for i = 1, 10 do
local k = '시그니처기술' .. i
local val = trim(args[k])
if val ~= '' then
table.insert(techs, val)
end
end
if #techs > 0 then
local tstr = '<ul style="list-style:none; margin:0; padding:0 0 0 10px; font-size:95%;">'
for i, tname in ipairs(techs) do
local marker = '①'
if i == 2 then marker = '②' end
if i == 3 then marker = '③' end
if i > 3 then marker = '•' end
tstr = tstr .. string.format('<li style="margin-top:2px;">%s %s</li>', marker, safe_html_encode(tname))
end
tstr = tstr .. '</ul>'
return tstr
end
return ''
end
-- 이미지 블록 (위키텍스트 반환)
local function build_image_block(args)
local main_file = trim(args['이미지_VN'] or args['이미지_애니'] or 'NoImage.png')
local output = {}
table.insert(output, string.format('[[파일:%s|250px|center]]', mw.text.encode(main_file)))
table.insert(output, '<div style="text-align:center; font-size:85%; margin-top:5px;">(애니 / VN 스위치)</div>')
return table.concat(output, '\n')
end
-- 이름 블록 (HTML 반환)
local function build_name_block(args)
local kr = trim(args['이름'] or '')
local jp = trim(args['이름_일어'] or '')
local en = trim(args['이름_영어'] or '')
local parts = {}
if kr ~= '' then
table.insert(parts, string.format('<strong style="font-size:1.1em;">이름: %s</strong>', safe_html_encode(kr)))
end
local sub_parts = {}
if jp ~= '' then table.insert(sub_parts, safe_html_encode(jp)) end
if en ~= '' then table.insert(sub_parts, safe_html_encode(en)) end
if #sub_parts > 0 then
table.insert(parts, string.format('<div style="font-size:90%%; margin-top:2px;">%s</div>', table.concat(sub_parts, ' / ')))
end
return table.concat(parts, '\n')
end
-- 라벨+값 행 생성 (값 없으면 빈 문자열)
local function info_row(label, value)
-- value가 nil이거나 빈 문자열이면 출력하지 않음
local val = trim(value)
if val == '' then return '' end
-- 목록 형태로 반환 ('- 라벨: 값' 형태)
return string.format('<div style="margin: 3px 0; padding-left: 10px;">- <strong>%s</strong>: %s</div>', safe_html_encode(label), safe_html_encode(val))
end
-- 나이 출력 규칙
local function build_age_row(args)
local nv = trim(args['나이_VN'])
local na = trim(args['나이_애니'])
local age_str = ''
if nv ~= '' and na ~= '' then
age_str = string.format('VN %s / Anime %s', safe_html_encode(nv), safe_html_encode(na))
elseif nv ~= '' then
age_str = string.format('VN %s', safe_html_encode(nv))
elseif na ~= '' then
age_str = string.format('Anime %s', safe_html_encode(na))
else
return ''
end
-- 나이 필드만 예외적으로 info_row 대신 직접 출력
if age_str ~= '' then
return string.format('<div style="margin: 3px 0; padding-left: 10px;">- <strong>나이</strong>: %s</div>', age_str)
end
return ''
end
function p.main(frame)
local parent = frame:getParent() or frame
local args = parent.args or {}
-- 모든 인수를 문자열로 트림 (nil 방지)
local clean_args = {}
for k, v in pairs(args) do
clean_args[k] = trim(v)
end
local theme = clean_args['색상'] ~= '' and clean_args['색상'] or '#007bff'
local image_block = build_image_block(clean_args)
local name_block = build_name_block(clean_args)
-- 섹션별 정보 구성
local basic = table.concat({
info_row('성별', clean_args['성별']),
build_age_row(clean_args),
info_row('생일', clean_args['생일']),
info_row('신장', clean_args['신장']),
info_row('체중', clean_args['체중']),
info_row('머리색', clean_args['머리색']),
info_row('눈색', clean_args['눈색']),
}, '\n')
local fc_techs = gather_techs(clean_args)
local fc = table.concat({
info_row('포지션', clean_args['포지션']),
}, '\n')
if fc_techs ~= '' then
fc = fc .. '<div style="margin: 3px 0; padding-left: 10px;">- <strong>시그니처 기술</strong>:</div>' .. fc_techs
end
local personal = table.concat({
info_row('학교', clean_args['학교']),
info_row('거주지', clean_args['거주지']),
info_row('취미', clean_args['취미']),
info_row('좋아하는 음식', clean_args['좋아하는음식']),
}, '\n')
local voice = table.concat({
info_row('일본어', clean_args['성우_JP']),
info_row('영어', clean_args['성우_EN']),
}, '\n')
local debut = table.concat({
info_row('애니메이션', clean_args['애니_데뷔']),
info_row('게임(VN)', clean_args['게임_데뷔']),
info_row('만화', clean_args['만화_데뷔']),
}, '\n')
-- ********** 위키 테이블 문자열 조립 **********
local wiki_table = {}
-- 1. 전체 컨테이너 테이블 시작 (2열: 이미지 / 정보)
table.insert(wiki_table, string.format('{| class="wikitable" style="width: 100%%; max-width: 650px; border: 2px solid %s; border-collapse: collapse; margin: 1em auto; font-size: 95%%;"', safe_html_encode(theme)))
table.insert(wiki_table, '|-')
-- 2. 좌측 열 (이미지 영역)
table.insert(wiki_table, string.format('| style="width: 35%%; vertical-align: top; padding: 10px; background-color: #f8f8f8;" | %s', image_block))
-- 3. 우측 열 (정보 영역)
table.insert(wiki_table, '| style="width: 65%%; vertical-align: top; padding: 10px;" |')
-- 우측 영역 내부 내용 (이름, 섹션 헤더, 정보 리스트)
local right_content = {}
table.insert(right_content, name_block)
table.insert(right_content, '<hr style="border: 0; border-top: 1px solid #ccc; margin: 8px 0;">')
local function add_section(title, content)
if trim(content) ~= '' then
table.insert(right_content, string.format('<h4 style="color:%s; margin:8px 0 4px 0; padding:0; font-size:1em; border-bottom:1px solid %s;"><%s></h4>', safe_html_encode(theme), safe_html_encode(theme), safe_html_encode(title)))
table.insert(right_content, content)
end
end
add_section('기본 정보', basic)
add_section('FC 프로필', fc)
add_section('개인 정보', personal)
add_section('성우', voice)
add_section('데뷔', debut)
table.insert(wiki_table, table.concat(right_content, '\n'))
-- 4. 테이블 닫기
table.insert(wiki_table, '|}')
return table.concat(wiki_table, '\n')
end
return p