267 lines
No EOL
10 KiB
Lua
267 lines
No EOL
10 KiB
Lua
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 |