feat: 实现 Pandoc DOCX 转换服务
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
local map_path = os.getenv("MD_TO_PDF_DOCX_MEDIA_MAP")
|
||||
|
||||
if map_path == nil or map_path == "" then
|
||||
error("DOCX media map path is missing")
|
||||
end
|
||||
|
||||
local map_file, open_error = io.open(map_path, "rb")
|
||||
if map_file == nil then
|
||||
error("DOCX media map cannot be opened: " .. tostring(open_error))
|
||||
end
|
||||
|
||||
local map_content = map_file:read("*a")
|
||||
map_file:close()
|
||||
|
||||
local media_map = pandoc.json.decode(map_content, false)
|
||||
local counters = {
|
||||
image = 0,
|
||||
mermaid = 0,
|
||||
echarts = 0
|
||||
}
|
||||
|
||||
local function contains_class(classes, expected)
|
||||
for _, class_name in ipairs(classes) do
|
||||
if class_name == expected then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function next_media(kind)
|
||||
counters[kind] = counters[kind] + 1
|
||||
local media = media_map[kind][counters[kind]]
|
||||
if media == nil then
|
||||
error(
|
||||
"DOCX media map does not contain "
|
||||
.. kind
|
||||
.. " #"
|
||||
.. tostring(counters[kind])
|
||||
)
|
||||
end
|
||||
return media
|
||||
end
|
||||
|
||||
local function media_image(media)
|
||||
return pandoc.Image(
|
||||
pandoc.Inlines { pandoc.Str(media.alt_text) },
|
||||
media.path,
|
||||
"",
|
||||
{
|
||||
width = media.width,
|
||||
height = media.height
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
local function replace_image(image)
|
||||
local media = next_media("image")
|
||||
image.src = media.path
|
||||
image.caption = pandoc.Inlines { pandoc.Str(media.alt_text) }
|
||||
image.attributes.width = media.width
|
||||
image.attributes.height = media.height
|
||||
return image
|
||||
end
|
||||
|
||||
local function replace_code_block(block)
|
||||
local kind
|
||||
if contains_class(block.classes, "mermaid") then
|
||||
kind = "mermaid"
|
||||
elseif contains_class(block.classes, "echarts") then
|
||||
kind = "echarts"
|
||||
else
|
||||
return block
|
||||
end
|
||||
|
||||
local media = next_media(kind)
|
||||
local image = media_image(media)
|
||||
if media.caption ~= nil and media.caption ~= "" then
|
||||
return pandoc.Figure(
|
||||
{ pandoc.Plain { image } },
|
||||
pandoc.Caption {
|
||||
pandoc.Plain { pandoc.Str(media.caption) }
|
||||
}
|
||||
)
|
||||
end
|
||||
return pandoc.Para { image }
|
||||
end
|
||||
|
||||
local function validate_counts()
|
||||
for _, kind in ipairs({ "image", "mermaid", "echarts" }) do
|
||||
if counters[kind] ~= #media_map[kind] then
|
||||
error(
|
||||
"DOCX media map contains unused "
|
||||
.. kind
|
||||
.. " resources"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
Pandoc = function(document)
|
||||
local transformed = document:walk {
|
||||
Image = replace_image,
|
||||
CodeBlock = replace_code_block
|
||||
}
|
||||
validate_counts()
|
||||
return transformed
|
||||
end
|
||||
}
|
||||
Reference in New Issue
Block a user