質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.30%
Pandoc

Pandocとは、Haskell製のライブラリおよびコマンドラインツールです。フリー且つオープンソースで、あるフォーマットで書かれた文書を別の形式へ変換することができます。対応フォーマットがとても多いことが特徴です。

Lua

Luaは、汎用のスクリプト言語の一つで、 移植性が高く、高速な実行速度などの特徴を持ち 手続き型・オブジェクト指向言語としても利用可能で 関数型言語、データ駆動型の要素も併せ持っている言語です。

Markdown

Markdownは、文書の構造、修飾情報を記述するための軽量マークアップ言語です。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

1回答

154閲覧

python,pandoc,luaフィルタで、太字(**)&指定font機能を実現し、docxファイルに書き込みたい

west_urad

総合スコア14

Pandoc

Pandocとは、Haskell製のライブラリおよびコマンドラインツールです。フリー且つオープンソースで、あるフォーマットで書かれた文書を別の形式へ変換することができます。対応フォーマットがとても多いことが特徴です。

Lua

Luaは、汎用のスクリプト言語の一つで、 移植性が高く、高速な実行速度などの特徴を持ち 手続き型・オブジェクト指向言語としても利用可能で 関数型言語、データ駆動型の要素も併せ持っている言語です。

Markdown

Markdownは、文書の構造、修飾情報を記述するための軽量マークアップ言語です。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2025/05/25 07:43

実現したいこと

①mdファイルの中に表がある。表の中には太字(**)がないものもあり、pythonで**を追記して、太字にしてdocxに書き込みたい。
②luaフィルタで、fontの場合、指定したsize,colorをdocxに書き込みたい。

発生している問題・分からないこと

①pythonで太字(**)を追記して修正できているが、それが表になっていない。原因はpandocが修正した表を認識しないからというのは理解できる。しかし、fix.luaでどのように実現方法がわからない。

②fix.luaで print(color),print(size)の結果、値は取得できている。しかし、docxファイルを見てみると、指定の色やサイズになっていない。

該当のソースコード

python(main.py)

1# ■■ Pandocインストール必要(https://github.com/jgm/pandoc/releases/tag/3.6.4) 2import os, re 3import subprocess 4from docx import Document 5import time 6 7# Markdownの整形処理 8def md_edit(md_file_path, temp_file_path): 9 with open(md_file_path, 'r', encoding='utf-8') as infile, open(temp_file_path, 'w', encoding='utf-8') as outfile: 10 prev_line_flg = False 11 for line in infile: 12 # リンク内のスペース除外 13 if '<a name="' in line and '</a>' in line: 14 start_index = line.find('<a name="') + len('<a name="') 15 end_index = line.find('">', start_index) 16 name_content = line[start_index:end_index].replace(' ', '').replace(' ', '') 17 line = line[:start_index] + name_content + line[end_index:] 18 19 # URL以外は、リンク内のスペース除外 20 if '](' in line and ')' in line and 'http' not in line: 21 start_index = line.find('](') + len('](') 22 end_index = line.find(')', start_index) 23 link_content = line[start_index:end_index].replace(' ', '').replace(' ', '') 24 line = line[:start_index] + link_content + line[end_index:] 25 26 # 行の先頭が<で次に英語または/以外の文字が続く場合、<を<に置換し、同じ行の次の>を>に置換 27 if re.match(r'^<[^a-zA-Z/]', line): 28 line = line.replace('<', '<', 1) 29 line = line.replace('>', '>', 1) 30 31 # "#"から始まる対象は、前回が空でなければ改行を追加 32 if line.startswith('#') and prev_line_flg: 33 outfile.write('\n') # 改行追加 34 35 # ">"のみの対象は、半角スペースを追加 36 if line.strip() == '>': 37 line = '> \n' 38 39 # 行の末尾が"<br>"または"<br/>"の場合、除外 40 if line.rstrip().endswith('<br>') and line.strip() != '<br>': 41 line = line.rstrip()[:-4] + '\n' 42 elif line.rstrip().endswith('<br/>') and line.strip() != '<br/>': 43 line = line.rstrip()[:-5] + '\n' 44 45 # 2025/04/29 全角スペースがあるときは、半角スペースに置換 46 if ' ' in line.strip(): 47 line = line.replace(' ',' ') 48 49 #2025/05/21 表の中の太字修正 50 # if ("|" in line.strip()) and ("| --" not in line.strip()) and ("jpg" not in line.strip()): 51 # parts = line.split("|")[1:-1] # '','\n'を削除(スライス) 52 53 # new_parts = [] 54 # for part in parts: 55 # part = part.strip() 56 57 # if ("*" in part) and ("**" not in part): 58 # part = part.replace("*","**") # *が1つ存在するときもあるので修正 59 # elif "*" not in part: 60 # part = f"**{part}**" # *が存在しないとき**追加 61 # new_parts.append(part) 62 # line = "| " + " | ".join(new_parts) + " |" 63 64 outfile.write(line) # 行反映 65 66 # 改行のみであったかチェック 67 if line == '\n': 68 prev_line_flg = False 69 else: 70 prev_line_flg = True 71 72# Word編集処理 73def word_edit(docx_file_path): 74 document = Document(docx_file_path) 75 for paragraph in document.paragraphs: 76 if paragraph.text.startswith('> '): 77 paragraph.style = 'Quote' 78 paragraph.text = paragraph.text[2:] 79 80 document.save(docx_file_path) 81 82def main(): 83 try: 84 # ◆Markdownファイルリスト 85 in_dir = "in" 86 md_file_lists = list(filter(lambda f: f.endswith(".md"), os.listdir(in_dir))) 87 88 # ◆ディレクトリ直下のカスタムテンプレートファイル 89 # Wordのスタイルウィンドウを出して、出力したいスタイルのテンプレートを作成 90 template_file = "template.docx" 91 92 # ◆出力ディレクトリ 93 out_dir = "out" 94 95 # ◆出力ディレクトリが存在しない場合は作成 96 if not os.path.exists(out_dir): 97 os.makedirs(out_dir) 98 99 # ◆Pandocコマンド実行 100 if md_file_lists: 101 for i, md_file in enumerate(md_file_lists): 102 input_file_path = os.path.join(in_dir, md_file) # インプットファイルパス作成 103 temp_file_path = os.path.join(in_dir, 'temp_' + md_file) # 一時ファイルパス作成 104 output_file_path = os.path.join(out_dir, os.path.splitext(os.path.basename(md_file))[0] + ".docx") # アウトプットファイルパス作成 105 106 md_edit(input_file_path, temp_file_path) # Markdownファイル整形 107 108 if template_file != "": 109 # ◇テンプレート指定あり 110 cmd = [ 111 "pandoc", temp_file_path, 112 "--reference-doc", template_file, 113 "--lua-filter=fix.lua", 114 "--wrap=preserve", 115 "-o", output_file_path 116 ] 117 else: 118 # ◇テンプレート指定なし 119 cmd = [ 120 "pandoc", temp_file_path, 121 "--lua-filter=fix.lua", 122 "--wrap=preserve", 123 "-o", output_file_path 124 ] 125 126 result = subprocess.run(cmd, capture_output=True, text=True) # Pandoc実行 127 if result.returncode != 0: 128 raise Exception(f"変換異常発生: {result.stderr} >{md_file}") 129 130 word_edit(output_file_path) # word編集 131 132 os.remove(temp_file_path) # 一時ファイル削除 133 134 if i == 0: print("【変換対象ファイル一覧】") 135 print(f"{i+1}: {md_file}") 136 137 print("-- 変換完了 --") 138 139 else: 140 print("-- MDファイルなし --") 141 142 except Exception as err: 143 print(err) 144 145 146if __name__ == '__main__': 147 main()

luaフィルタ(fix.lua)

1local b_cnt = 0 2 3-- ブロック処理 4function RawBlock(el) 5 -- 改ページ処理 6 if el.text == '<div style="page-break-before:always"></div>' then 7 return pandoc.RawBlock('openxml', '<w:p><w:r><w:br w:type="page"/></w:r></w:p>') 8 else 9 return el 10 11 end 12end 13 14-- インライン処理 15function RawInline(el) 16 -- 2025/05/19下線処理 17 if el.text == '<u>' then 18 return pandoc.RawInline('openxml', '<w:r><w:rPr><w:u w:val="single"/><w:t>') 19 20 -- 2025/05/19下線処理 21 elseif el.text == '</u>' then 22 return pandoc.RawInline('openxml', '</w:t></w:rPr></w:r>') 23 24 -- 改行処理 25 elseif el.text == '<br>' or el.text == '<br/>' then 26 return pandoc.RawInline('openxml', '<w:br/>') 27 28 -- ブックマーク処理 29 elseif el.text:match('<a name="(.-)">') then 30 local b_name = el.text:match('<a name="(.-)">') 31 b_cnt = b_cnt + 1 32 return pandoc.RawInline('openxml', '<w:bookmarkStart w:id="' .. b_cnt .. '" w:name="' .. b_name .. '"/><w:bookmarkEnd w:id="' .. b_cnt .. '"/>') 33 34 -- font処理 size,colorの場合がある。(問題部分はここ) 35 elseif el.text:find("<font") then 36 local color = el.text:match('color[ ]*=[ ]*["\']?#?([%w]+)["\']?') 37 local size = el.text:match('size[ ]*=[ ]*["\']?([%w]+)["\']?') 38 local openxml = '<w:r><w:rPr>' 39 40 if color then 41 openxml = openxml .. '<w:color w:val="' .. color .. '"/>' 42 end 43 44 if size then 45 -- OpenXML のフォントサイズは半ポイント単位(例: size=2 → 16pt → val="32") 46 local size_val = tonumber(size) and tostring(tonumber(size) * 8) or nil 47 if size_val then 48 openxml = openxml .. '<w:sz w:val="' .. size_val .. '"/>' 49 end 50 end 51 52 openxml = openxml .. '<w:t>' 53 return pandoc.RawInline('openxml', openxml) 54 55 elseif el.text == '</font>' then 56 --print(el.text) 57 return pandoc.RawInline('openxml', '</w:t></w:rPr></w:r>') 58 59 60 else 61 return el 62 63 end 64end 65 66-- リンク処理 67function Link(el) 68 if el.target:match("^#") then 69 b_cnt = b_cnt + 1 70 local b_name = el.target:sub(2):gsub("%%20", " ") 71 local content = pandoc.utils.stringify(el.content) 72 return { 73 pandoc.RawInline('openxml', '<w:bookmarkStart w:id="' .. b_cnt .. '" w:name="' .. b_name .. '"/>'),pandoc.RawInline('openxml', '<w:hyperlink w:anchor="' .. b_name .. '"><w:r><w:t>' .. content .. '</w:t></w:r></w:hyperlink>'), 74 pandoc.RawInline('openxml', '<w:bookmarkEnd w:id="' .. b_cnt .. '"/>') 75 } 76 else 77 return el 78 end 79end

sample.md

1| アイウエオ | (pythonでアイウエオに**をつけて太字にしたい) 2| ------------------------------------------------------------ | 3| *<font color=e85298>ユーザー名:〜〜〜</font>*<br/>*<font color=e85298>パスワード:〜〜〜</font>* | (pythonで*を**にしたい) 4| **カキクケコ** | (カキクケコは問題ない) 5| *<font color=e85298>担当者ID:〜〜〜</font>*<br>*<font color=e85298> パスワード:〜〜〜</font>* | (pythonで*を**にしたい)

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

python,luaフィルタのコードのエラーはなく、docxファイルは作成される。
①上記のコードを書いたが、表になっていない。
②指定の色やサイズになっていない。

補足

python 3.8.10
pandoc 3.6.4
OS:Windows

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

私が質問の意図を取り違えていたらすいません。

後述のコードではテーブルの先頭行を強調するのと

text

1*なにかの文字列*

text

1**なにかの文字列**

のように単体の * を2個の ** として置き換えるコードを提案しています。
(こちらについては Copilot に訪ねたのを白状しておきます)

以下はコードの修正点です。

md_edit メソッド内を2箇所修正することとなります。

python

1 prev_line_flg = False

の行の下に以下のコード、

python

1 table_begins = False # テーブルの先頭行で True になる

そして

python

1 outfile.write(line) # 行反映

の行の上に以下のコード

python

1 # 2025/05/30 ++++++++++++ここから 2 # テーブル見出しを強調する 3 table_header = line 4 if not table_begins and re.search(r'^\| +', table_header): 5 table_header = re.sub('^(\| +)', '\\1**', table_header) 6 table_header = re.sub('( +\|)', '**\\1', table_header) 7 line = table_header 8 table_begins = True 9 elif table_begins and re.search(r'^\| +', table_header) == None: 10 table_begins = False 11 12 # * を ** に置き換える 13 line = re.sub('(?<!\*)\*(?!\*)', '**', line) 14 # 2025/05/30 ++++++++++++ここまで

を加えてください。

投稿2025/06/04 14:54

kenjiro_n

総合スコア8

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.30%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問