mireapandoc/filter2.lua

267 lines
10 KiB
Lua
Raw Permalink Normal View History

2025-02-15 02:42:21 +03:00
IMAGE, FORMULA, TABLE, CODE = 0, 0, 0, 0
UNNUMBERED = true
local hdlis = {
['АННОТАЦИЯ'] = true,
['ВВЕДЕНИЕ'] = true,
['ЗАКЛЮЧЕНИЕ'] = true,
['ВЫВОДЫ'] = true,
['СПИСОК-ИСПОЛЬЗОВАННЫХ-ИСТОЧНИКОВ'] = true
}
local section = {0, 0, 0}
local function remove_trailing_punctuation(text)
return text:gsub("[,;%.!?]+$", "")
end
local function capitalize_first_letter(text)
local first_char = pandoc.text.sub(text, 1, 1)
local rest = pandoc.text.sub(text, 2)
return pandoc.text.upper(first_char) .. rest
end
local function is_unnumbered(header_text)
return hdlis[pandoc.text.upper(header_text)] ~= nil
end
local function get_section_number(level)
if level == 1 then
section[1] = section[1] + 1
section[2] = 0
section[3] = 0
IMAGE = 0
return tostring(section[1])
elseif level == 2 then
if section[1] == 0 then
io.stderr:write("Ошибка: Второй уровень без первого запрещен\n")
return nil
end
section[2] = section[2] + 1
section[3] = 0
return section[1] .. "." .. section[2]
elseif level >= 3 then
if section[1] == 0 or section[2] == 0 then
io.stderr:write("Ошибка: Третий уровень без первого или второго запрещен\n")
return nil
end
section[3] = section[3] + 1
return section[1] .. "." .. section[2] .. "." .. section[3]
end
end
function Header(el)
local cleaned_text = capitalize_first_letter(remove_trailing_punctuation(pandoc.utils.stringify(el.content)))
local is_unnumbered_section = is_unnumbered(cleaned_text)
UNNUMBERED = is_unnumbered_section
local level = math.min(el.level, 3) -- Принудительно ограничиваем вложенность до 3
if level > 1 and section[1] == 0 then
io.stderr:write("Ошибка: Заголовки 2 и ниже без нумерованного заголовка 1 уровня\n")
return el
end
if not is_unnumbered_section then
local section_number = get_section_number(level)
cleaned_text = section_number .. " " .. capitalize_first_letter(cleaned_text)
elseif level > 1 then
io.stderr:write("Ошибка: В ненумеруемом разделе не должно быть вложенных заголовков\n")
return el
end
el.content = pandoc.Inlines(cleaned_text)
return el
end
function Figure(el)
IMAGE = IMAGE + 1
local str = pandoc.utils.stringify(el.caption)
str = capitalize_first_letter(str)
str = remove_trailing_punctuation(str)
if UNNUMBERED then
io.stderr:write("Ошибка: Рисунок " .. str .. " в ненумерованном разделе!")
return el
end
str = "Рисунок " .. section[1] .. "." .. IMAGE .. "" .. str
el.caption = pandoc.Blocks(str)
return el
end
function Table(el)
TABLE = TABLE + 1
local str = pandoc.utils.stringify(el.caption)
str = capitalize_first_letter(str)
str = remove_trailing_punctuation(str)
if UNNUMBERED then
io.stderr:write("Ошибка: Таблица " .. str .. " в ненумерованном разделе!")
return el
end
str = "Таблица " .. section[1] .. "." .. IMAGE .. "" .. str
el.caption = pandoc.Blocks(str)
return el
end
--[[
Предполагается, что ранее в фильтре уже определены:
IMAGE, FORMULA, TABLE, CODE = 0, 0, 0, 0
UNNUMBERED, hdlis, section
функции Header, Figure, Table (обрабатывающие подписи и прочее)
Этот блок добавляет модификацию соседних блоков (предыдущего Para для ссылки,
а также оборачивание следующего блока для Table и CodeBlock).
--]]
-- Функция для приведения первой буквы строки к заглавной
function capitalize_first_letter(text)
local first = pandoc.text.sub(text, 1, 1)
local rest = pandoc.text.sub(text, 2)
return pandoc.text.upper(first) .. rest
end
-- Функция оборачивает блок в Div с кастомным стилем "aftertable"
function wrap_aftertable(block)
return pandoc.Div(block, pandoc.Attr("", {}, {["custom-style"] = "aftertable"}))
end
-- Функция, которая ищет в списке блоков блок по индексу index и, если это Para (или вложенный Para в Div),
-- удаляет завершающую пунктуацию у последнего текстового элемента и дописывает ссылку в виде:
-- " (Тип section.num):"
function append_reference_to_block(blocks, index, ref_name, section_num, num)
if index < 1 or index > #blocks then
return false
end
local blk = blocks[index]
-- Если блок Div, пробуем взять последний вложенный блок из его содержимого
if blk.t == "Div" then
if blk.c and #blk.c >= 2 and type(blk.c[2]) == "table" and #blk.c[2] > 0 then
blk = blk.c[2][#blk.c[2]]
else
return false
end
end
if blk.t ~= "Para" then
return false
end
-- Удаляем завершающую пунктуацию у последнего Str
local inlines = blk.c
for j = #inlines, 1, -1 do
if inlines[j].t == "Str" then
inlines[j].text = inlines[j].text:gsub("[%.?!…%s]+$", "")
break
end
end
-- Формируем текст ссылки, например: "(Рисунок 1.2):"
local ref_text = "(" .. ref_name .. " " .. section_num .. "." .. num .. "):"
ref_text = capitalize_first_letter(ref_text)
table.insert(inlines, pandoc.Space())
table.insert(inlines, pandoc.Str(ref_text))
return true
end
-- Главная функция фильтра, которая проходит по всем блокам документа и в нужных местах
-- модифицирует соседние блоки.
function Pandoc(doc)
local blocks = doc.blocks
local i = 1
while i <= #blocks do
local blk = blocks[i]
if blk.t == "Table" then
-- Для таблиц: добавляем ссылку в предыдущий абзац и оборачиваем следующий блок
if not append_reference_to_block(blocks, i - 1, "Таблица", section[1], TABLE) then
io.stderr:write("ОШИБКА: перед таблицей нет абзаца для ссылки\n")
end
if i + 1 <= #blocks then
blocks[i + 1] = wrap_aftertable(blocks[i + 1])
end
elseif blk.t == "Figure" then
-- Для рисунков: добавляем ссылку в предыдущий абзац
if not append_reference_to_block(blocks, i - 1, "Рисунок", section[1], IMAGE) then
io.stderr:write("ОШИБКА: перед рисунком нет абзаца для ссылки\n")
end
elseif blk.t == "CodeBlock" then
-- Для листингов: проверяем наличие подписи (caption) в атрибутах
local attr = blk.attr or {"", {}, {}}
local attributes = attr[3] or {}
local caption = attributes.caption
if not caption then
io.stderr:write("ОШИБКА: Листинг без подписи!\nИспользуйте ключ caption в CodeBlock\n")
else
CODE = CODE + 1
local ref_text = "Листинг " .. section[1] .. "." .. CODE .. "" .. caption
if not append_reference_to_block(blocks, i - 1, "Листинг", section[1], CODE) then
io.stderr:write("ОШИБКА: перед листингом нет абзаца для ссылки\n")
end
-- Вставляем блок с подписью перед текущим CodeBlock.
local caption_block = pandoc.Div(
pandoc.Para({ pandoc.Str(capitalize_first_letter(ref_text)) }),
pandoc.Attr("", {}, {["custom-style"] = "Code Caption"})
)
table.insert(blocks, i, caption_block)
i = i + 1 -- пропускаем вставленный блок
if i + 1 <= #blocks then
blocks[i + 1] = wrap_aftertable(blocks[i + 1])
end
end
end
i = i + 1
end
return pandoc.Pandoc(blocks, doc.meta)
end
function Meta(m)
if m.date == nil then
m.date = os.date("%e «%m» %Y")
end
return m
end
local function remove_trailing_punctuation_li (li)
return pandoc.Blocks(remove_trailing_punctuation(pandoc.utils.stringify(li)))
end
local function add_trailing_semicolon_li (li)
return pandoc.Blocks(pandoc.utils.stringify(li) .. ";")
end
local function add_trailing_stop_li (li)
return pandoc.Blocks(pandoc.utils.stringify(li) .. ".")
end
local function capitalize_first_letter_li (li)
return pandoc.Blocks(capitalize_first_letter(pandoc.utils.stringify(li)))
end
local function warnCapitalizedStart (li)
local str = pandoc.utils.stringify(li)
local capitalized = capitalize_first_letter(str)
if str == capitalized
then
io.stderr:write("Предупреждение: необходимо исправить вручную. В маркированном списке предложение должно начинаться со строчной (маленькой) буквы. Если текст пункта стартует с аббревиатуры, проигнорируйте данное предупреждение, иначе - исправьте вручную. Текст пункта:\n" .. str)
end
return li
end
function BulletList(el)
local li = el.content
li = pandoc.List.map(li, remove_trailing_punctuation_li)
li = pandoc.List.map(li, add_trailing_semicolon_li)
local ending = pandoc.List.at(el.content, -1)
pandoc.List.remove(li)
ending = add_trailing_stop_li(remove_trailing_punctuation_li(ending))
pandoc.List.insert(li, ending)
li = pandoc.List.map(li, warnCapitalizedStart);
el.content = li
return el
end
function OrderedList(el)
local li = el.content
li = pandoc.List.map(li, remove_trailing_punctuation_li)
li = pandoc.List.map(li, add_trailing_stop_li)
li = pandoc.List.map(li, capitalize_first_letter_li)
el.content = li
return el
end