ข้ามไปเนื้อหา

มอดูล:ja-parse

จาก วิกิพจนานุกรม พจนานุกรมเสรี

local export = {}

local len = mw.ustring.len
local sub = mw.ustring.sub
local gsub = mw.ustring.gsub
local find = mw.ustring.find
local match = mw.ustring.match
local gmatch = mw.ustring.gmatch

local m_ja = require('Module:ja')

local kanji_pattern = "㐀-䶵一-鿌\239\164\128-\239\171\153𠀀-𯨟"
local kana_pattern = 'ぁ-ゖァ-ヺー'
local japanese_pattern = kana_pattern .. kanji_pattern .. 'a-zA-Z0-9〆々'

local headword_templates = {
	['ja-adj'] = true, ['ja-pos'] = true, ['ja-noun'] = true, ['ja-phrase'] = true,
	['ja-verb'] = true, ['ja-verb form'] = true, ['ja-verb-suru'] = true, 
}

local function find_headword_template(wikitext)
	local index =
		wikitext:find('{{ja%-adj[|}]') or
		wikitext:find('{{ja%-pos[|}]') or
		wikitext:find('{{ja%-noun[|}]') or
		wikitext:find('{{ja%-phrase[|}]') or
		wikitext:find('{{ja%-verb[|}]') or
		wikitext:find('{{ja%-verb form[|}]') or
		wikitext:find('{{ja%-verb%-suru[|}]')
	if index then
		-- This assumes that the template has matching braces.
		return wikitext:match('%b{}', index)
	end
end

local function parse_template(wikitext) -- only supports the simplest format
	local template = wikitext
	template = template:gsub('%[%[([^%[%]|]-)|([^%[%]|]-)%]%]', '[[%1`%2]]')
	local name
	local args = {}
	for glob in mw.text.gsplit(template:gsub('^{{', ''):gsub('}}$', ''), '|') do
		if not name then
			name = glob
		else
			glob = glob:gsub('`', '|')
			local key, value = match(glob, "(.-)=(.*)")
			if key and value then
				args[key] = value
			else
				table.insert(args, glob)
			end
		end
	end
	return name, args
end

local function contains(list, item)
	for i = 1, #list do
		if list[i] == item then return true end
	end
	return false
end


-- A function to parse Japanese entries, returning a list of etym sections, each having the form { wikitext, type = ( 'lemma' | 'redirect' | '' ), spellings = <a list of the term's modern spellings>, historical_spellings = <a list of the term's historical kana spellings> }. In case of multiple etymologies, each ===Etymology n=== part constitutes an etym section. Otherwise, the whole Japanese section minus any ===Kanji [n]=== subsections constitutes a single etym section.
-- Note: The function divides sections strictly by L3 headers. As a result:
-- (1) If an entry describes both a kanji and a single word, any templates beginning the word (such as {{ja-spellings}}) will be erroneously considered part of the kanji section above. This function only remedies the cases of {{ja-spellings}} and {{ja-kanjitab}}, by inserting an empty header === === above it before parsing. (This problem is absent for entries with multiple etymologies, since each word must begin with ===Etymology n===.)
-- (2) If an entry describes multiple words, word-specific templates such as {{topics|ja|Biology}} must now be placed at the end of the relevant word instead of the whole entry. If they are put at the end of the ==Japanese== entry, they will be either erroneously considered part of the final word or additional sections such as ===References===, and ignored when {{ja-see}} copies categories around.

function export.extract_etym_sections(lemma)
	local page = mw.title.new(lemma):getContent() or ''
	--remove space that most of thai wiktionarian do
	page = gsub(page,'== *','==')
	page = gsub(page,' *==','==')
	--local l2 = mw.ustring.match(page .. '\n----\n', '==Japanese==\n(.-)%-%-%-%-')
	local l2 = mw.ustring.match(page, '==ภาษาญี่ปุ่น==\n(.-)%-%-%-%-') or mw.ustring.match(page, '==ภาษาญี่ปุ่น==\n(.*)')
	
	-- split into L3 sections
	local l3_sections = {}
	local multi_etym = false
	
	if l2 then
		-- another space removed
		l2 = gsub(l2, '=== *','===')
		l2 = gsub(l2, '* ===','===')
		-- special hack mentioned above
		if not find(l2, '===รากศัพท์ 1===') and (find(l2, '===คันจิ===') or find(l2, '===คันจิ %d+===')) then
			l2 = gsub(l2, '{{ja%-spellings', '=== ===\n{{ja-spellings')
			l2 = gsub(l2, '{{ja%-kanjitab', '=== ===\n{{ja-kanjitab')
		end
		
		local current_l3_title = ''
		local current_l3_content = {}
		for v in l2:gmatch('[^\n]+') do
			if find(v, '^===[^=]') then
				table.insert(l3_sections, { current_l3_title, table.concat(current_l3_content, '\n') })
				current_l3_title = match(v, '^===([^=]+)')
				if current_l3_title == 'รากศัพท์ 1' then multi_etym = true end
				current_l3_content = {}
			end
			table.insert(current_l3_content, v)
		end
		table.insert(l3_sections, { current_l3_title, table.concat(current_l3_content, '\n') })
	end
	
	-- group the L3 sections into etym sections
	local etym_sections = {}
	if multi_etym then
		for _, v in ipairs(l3_sections) do
			local header = v[1]
			local content = v[2]
			if find(header, '^รากศัพท์ %d+$') then
				table.insert(etym_sections, content)
			end
		end
	else
		local word = {}
		for _, v in ipairs(l3_sections) do
			local header = v[1]
			local content = v[2]
			if not (header == 'คันจิ' or find(header, '^คันจิ %d+$')) then
				table.insert(word, content)
			end
		end
		word = table.concat(word, '\n')
		table.insert(etym_sections, word)
	end
	
	-- finally, determine the type of each etym section
	for i = 1, #etym_sections do
		etym_section = etym_sections[i]
		local ja_see = find(etym_section, '{{ja%-see[|}]') or find(etym_section, '{{ja%-see-kango[|}]')
		if ja_see then
			local spellings = { lemma }
			for v in gmatch(match(etym_section, '.-}}', ja_see), '[' .. japanese_pattern .. ']+') do
				table.insert(spellings, v)
			end
			etym_sections[i] = { etym_section, type = 'redirect', spellings = spellings, historical_spellings = {} }
		else
			local ja_forms = etym_section:find('{{ja%-spellings[|}]')
			if ja_forms then
				local spellings = { lemma }
				local historical_spellings = {}
				local name, args = parse_template(etym_section:match('%b{}', ja_forms))
				for i = 1, #args do 
					table.insert(spellings, args[i])
				end
				table.insert(historical_spellings, args['h'])
				for i = 2, 5 do 
					table.insert(historical_spellings, args['h' .. i])
				end
				if args['h6'] then error('ja-parse: I don\'t support more than five historical spellings at the moment. Please expand me.') end
				etym_sections[i] = { etym_section, type = 'lemma', spellings = spellings, historical_spellings = historical_spellings }
			else
				local headword_template = find_headword_template(etym_section)
				if headword_template then
					local spellings = { lemma }
					local historical_spellings = {}
					local name, args = parse_template(headword_template)
					for i = 1, #args do
						local candidate = args[i]
						if find(candidate, '[' .. japanese_pattern .. ']') then
							table.insert(spellings, m_ja.remove_ruby_markup(candidate))
						end
					end
					table.insert(historical_spellings, args.hhira)
					table.insert(historical_spellings, args.hkata)
					etym_sections[i] = { etym_section, type = 'lemma', spellings = spellings, historical_spellings = historical_spellings }
				else
					etym_sections[i] = { etym_section, type = '', spellings = {}, historical_spellings = {} }
				end
			end
		end
	end
	return etym_sections
end

-- A function to parse Japanese entries based to the function above, but filters the result and finds the etym section with the alternative spelling given by the spelling, and returns it as wikitext.
function export.get_etym_section(lemma, spelling)
	local words = export.extract_etym_sections(lemma)
	local result = {}
	local spellings = {}
	for _, v in ipairs(words) do
		if v.type == 'lemma' and contains(v.spellings, spelling) then
			table.insert(result, v[1])
			for _, spelling in ipairs(v.spellings) do
				if not contains(spellings, spelling) then table.insert(spellings, spelling) end
			end
		end
	end
	local wikitext = table.concat(result, '\n')
	return wikitext, spellings
end

function export.extract_definitions_and_categories(wikitext, lemma, alt_spelling, frame)
	local def = {}
	local cat = {}
	local current_section = ''
	
	for v in wikitext:gmatch('[^\n]+') do
		if v:find('^#+ ') then
			if not v:find('{{rfdef') and not (v:find('{{ja%-def|') and not v:find('|' .. alt_spelling .. '[|}]') and find(alt_spelling, '[' .. kanji_pattern .. ']')) then
				table.insert(def, { v, pos = current_section })
			end
		elseif v:find('^===') then
			current_section = v:gsub("^=*(.-)=*$", "%1")
		else
			table.insert(cat, v)
		end
	end
	
	-- expand the other parts for categories
	local cat = table.concat(cat, '\n')
	cat = gsub(cat, '<ref', '')
	local function process_template_header(a, b) -- if the template begins with "{{ja-usex|", a is "ja-usex" and b is "|". 
		local templates_to_exclude = {
			-- These templates are ignored as an optimization since they don't generate categories.
			['m'] = true, ['l'] = true, ['ja-l'] = true, ['ja-r'] = true, ['gloss'] = true,
			['w'] = true, ['wp'] = true, ['swp'] = true, ['wikipedia'] = true,
			['lang'] = true, ['furigana'] = true, ['wj'] = true, ['lj'] = true, ['ruby/ja-w2'] = true, ['ruby/ja'] = true, ['ruby'] = true,
			['ja-kanji forms'] = true, ['w2'] = true, ['sense'] = true, 
			['IPAfont'] = true, ['IPAchar'] = true,
			['ja-adj-infl'] = true, ['ja-i'] = true, ['ja-na'] = true, ['ja-adjdecl'] = true, ['ja-decl-na'] = true, ['ja-go-bu'] = true, ['ja-go-gu'] = true, ['ja-go-ku'] = true, ['ja-go-mu'] = true, ['ja-go-nu'] = true, ['ja-go-ou'] = true, ['ja-go-ru'] = true, ['ja-go-su'] = true, ['ja-go-tsu'] = true, ['ja-go-u'] = true, ['ja-honorific'] = true, ['ja-ichi'] = true, ['ja-kuru'] = true, ['ja-suru'] = true, ['ja-suru-i-ku'] = true, ['ja-suru-tsu'] = true, ['ja-verbconj'] = true, ['ja-verbconj-auto'] = true, ['ja-verbconj-row'] = true, ['ja-verbconjugation'] = true, ['ja-zuru'] = true, 
			['ja-kanji spellings'] = true, ['ja-ks'] = true, ['ja-spellings'] = true, ['ja-forms'] = true,
			['Japanese first-person pronouns'] = true, ['der-top'] = true, ['der-bottom'] = true, 
			['der-mid'] = true, ['der-top3'] = true, ['der-top4'] = true, ['der-top5'] = true, ['rel-top'] = true, 
			['ja-uk'] = true,
			
			-- These templates are ignored since they generate categories that are spelling-specific or that we're not interested in
			['ja-kanjitab'] = true, ['ateji'] = true, ['ja-ateji'] = true, ['ja-kanji'] = true, ['ja-readings'] = true, 
			['juku'] = true, ['jukujikun'] = true, ['ja-jukujikun'] = true, 
			['ja-def'] = true, ['synonyms'] = true,
		}
		if templates_to_exclude[a] then
			return '{{=' .. b
		elseif headword_templates[a] then
			local source_script = m_ja.script(lemma)
			if source_script == 'Hira' or source_script == 'Kana' or source_script == 'Hira+Kana' then
				return '{{' .. a .. '|hira=' .. lemma .. b
			else
				return '{{' .. a .. b
			end
		elseif a == 'ja-usex' or a:find('^quote') then -- special hack
			return '[[Category:Japanese terms with usage examples]]{{=' .. b
		else
			return '{{' .. a .. b
		end
	end
	cat = gsub(cat, '{{([^|}\n]+)\n?([|}])', process_template_header)
	cat = gsub(cat, '{{ja%-pron.-}}', function(pron)
		local result = ''
		if not find(pron, '|noipa=') then result = result .. '[[Category:Japanese terms with IPA pronunciation]]' end
		if find(pron, '|a=') or find(pron, '|audio=') then result = result .. '[[Category:Japanese terms with audio links]]' end
		return result
		end)
	cat = frame:preprocess(cat)
	
	local cat2 = {}
	for i in gmatch(cat, '%[%[Category:.-%]%]') do table.insert(cat2, i) end
	cat = table.concat(cat2)
	-- one might want to modify the sortkeys here
	
	return def, cat
end

--[[
function export.fetch_reading(title, _wikitext) -- the _wikitext parameter is only used for recursion
	local wikitext
	if _wikitext then
		wikitext = _wikitext
	else
		local page = mw.title.new(title):getContent() or ''
		local l2 = page:match('==Japanese==\n(.-)%-%-%-%-') or page:match('==Japanese==\n(.*)') or ''
		if l2 == '' or l2:find('===Etymology 1===') then
			wikitext = ''
		else
			wikitext = l2
		end
	end
	
	local kana
	local romaji
	
	local headword_template = find_headword_template(wikitext)
	if headword_template then
		for glob in mw.text.gsplit(headword_template:gsub('%}%}', '|'), '|') do
			if match(glob, '^[%s%.%-%^' .. kana_pattern .. '、]+$') then
				kana = glob; break
			end
		end
		romaji = headword_template:match("%|rom%=([^%|%}]+)")
	end
	
	if not kana and match(title, '^[' .. kana_pattern .. '、]+$') then
		kana = title
	end
	
	if not kana then
		if _wikitext then
			return nil
		else
			local main_entry = wikitext:match('{{ja%-see|(.-)[|}]')
			if main_entry then
				local new_wikitext = export.get_etym_section(main_entry, title)
				return export.fetch_reading(main_entry, new_wikitext:gsub('===Etymology %d+===', ''))
			else
				return nil
			end
		end
	end
	if not romaji then
		if headword_template:find('ja%-verb%|') then
			kana = gsub(kana, 'う$', '.う')
		elseif headword_template:find('ja%-adj') and (headword_template:find('%|infl=i') or headword_template:find('%|infl=い') or headword_template:find('%|decl=i') or headword_template:find('%|decl=い')) then
			kana = gsub(kana, 'い$', '.い')
		elseif headword_template:find('proper') then
			kana = gsub(kana, '^', '^')
			kana = gsub(kana, ' ', ' ^')
			kana = gsub(kana, '%-', '-^')
		end
		romaji = m_ja.kana_to_romaji(kana)
	end
	
	kana = gsub(kana, '[%s%.%-%^]', '')
	return kana, romaji
	
end
]]
return export