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