tugaa commited on
Commit
ddbdd30
·
verified ·
1 Parent(s): d0130c9

Update mainapp.py

Browse files
Files changed (1) hide show
  1. mainapp.py +257 -93
mainapp.py CHANGED
@@ -1,81 +1,172 @@
1
- ## 依存ライブラリのインストール
2
-
3
- ##このコードを実行するには、以下のライブラリが必要です。
4
-
5
- ##```bash
6
- ##pip install janome jaconv
7
-
8
  import os
9
  import re
 
 
10
  from janome.tokenizer import Tokenizer
11
  import jaconv
 
 
 
 
 
12
 
13
- # 設定
14
- input_folder = './aozora_texts'
15
- output_folder = './comparison_output'
16
 
17
- def extract_hiragana(text):
18
- """
19
- テキストデータから可能な限りひらがなを抽出します(漢字は読みをひらがなで)。
20
- """
21
- tokenizer = Tokenizer() # udic パラメータを削除
22
- hiragana_words = []
23
- for token in tokenizer.tokenize(text):
24
- if token.reading != '*':
25
- # 読みが存在する場合は、カタカナをひらがなに変換して追加
26
- hiragana_words.append(jaconv.kata2hira(token.reading))
27
- elif all('\u3041' <= char <= '\u3096' for char in token.surface):
28
- # 読みがなく、表面形がひらがなの場合はそのまま追加
29
- hiragana_words.append(token.surface)
30
- elif token.part_of_speech.startswith('記号'):
31
- # 記号はそのまま追加
32
- hiragana_words.append(token.surface)
33
- return "".join(hiragana_words)
34
-
35
- def preprocess_text(text):
36
- """
37
- テキストデータの前処理を行います(行ごと処理)。
38
- 改行コードの削除、空白の正規化、およびカタカナへの変換を行います。
39
- """
40
- text = text.replace('\r\n', '\n').replace('\r', '\n').strip()
41
- text = re.sub(r'\s+', ' ', text)
42
-
43
- tokenizer = Tokenizer() # udic パラメータを削除
44
- katakana_words = []
45
- for token in tokenizer.tokenize(text):
46
- if token.reading == '*':
47
- katakana_words.append(token.surface)
48
- else:
49
- katakana_words.append(token.reading)
50
 
51
- return "".join(katakana_words)
 
52
 
53
- def read_text_with_bom_removal(filepath, encoding='utf-8'):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  """
55
  BOM付きの可能性のあるテキストファイルを読み込み、BOMを取り除いて返します。
 
 
 
 
 
 
 
 
 
 
56
  """
57
- with open(filepath, 'rb') as f:
58
- raw_data = f.read()
 
59
 
60
- if raw_data.startswith(b'\xef\xbb\xbf'):
61
- return raw_data[3:].decode(encoding)
62
- else:
63
- return raw_data.decode(encoding)
 
 
 
 
 
 
64
 
65
- def output_comparison_data(filename, original_text, preprocessed_text, hiragana_text, output_folder):
66
  """
67
- 元のテキスト、カタカナ変換後のテキスト、ひらがな抽出後のテキストを行ごとにタブ区切りでファイルに出力します。
68
- 出力フォルダを引数として受け取ります。
69
  """
70
  if not os.path.exists(output_folder):
71
- os.makedirs(output_folder)
 
 
 
 
 
 
 
72
 
73
  base_filename, ext = os.path.splitext(filename)
74
- output_filepath = os.path.join(output_folder, f"{base_filename}_comparison.tsv")
 
75
 
76
  try:
77
- with open(output_filepath, 'w', encoding='utf-8', errors='ignore') as outfile:
78
- outfile.write("Original\tKatakana\tHiragana\n")
 
 
79
  original_lines = original_text.splitlines()
80
  preprocessed_lines = preprocessed_text.splitlines()
81
  hiragana_lines = hiragana_text.splitlines()
@@ -85,61 +176,134 @@ def output_comparison_data(filename, original_text, preprocessed_text, hiragana_
85
  original = original_lines[i] if i < len(original_lines) else ""
86
  preprocessed = preprocessed_lines[i] if i < len(preprocessed_lines) else ""
87
  hiragana = hiragana_lines[i] if i < len(hiragana_lines) else ""
88
- outfile.write(f"{original}\t{preprocessed}\t{hiragana}\n")
89
 
90
- print(f"比較データを '{output_filepath}' に出力しました。")
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
  except Exception as e:
93
- print(f"エラー: ファイル '{output_filepath}' への書き込みに失敗しました: {e}")
94
 
95
- def process_file(filename, input_folder, output_folder):
96
  """
97
- 指定されたファイル名で、input_folder内のテキストファイルを読み込み、
98
- 行ごとに前処理(カタカナ変換)とひらがな抽出を行い、比較データを出力します。
 
 
 
 
 
 
 
 
99
  """
100
- filepath = os.path.join(input_folder, filename)
101
  try:
102
  original_text = read_text_with_bom_removal(filepath)
103
  original_lines = original_text.splitlines()
104
- preprocessed_lines = []
105
- hiragana_lines = []
106
 
107
  for line in original_lines:
108
  if line.strip():
109
- preprocessed_line = preprocess_text(line)
110
- hiragana_line = extract_hiragana(line)
111
  preprocessed_lines.append(preprocessed_line)
112
  hiragana_lines.append(hiragana_line)
113
  else:
114
- preprocessed_lines.append("") # 空行を保持
115
- hiragana_lines.append("") # 空行を保持
116
 
117
- output_comparison_data(filename, original_text, "\n".join(preprocessed_lines), "\n".join(hiragana_lines), output_folder)
118
 
119
- except FileNotFoundError:
120
- print(f"エラー: ファイル '{filepath}' が見つかりません。")
121
  except Exception as e:
122
- print(f"エラー: ファイル '{filepath}' の処理中にエラーが発生しました: {e}")
123
 
124
- def load_text_files(folder_path):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  """
126
- 指定されたフォルダ内の .txt ファイルのリストを返します。
127
  """
128
- text_files = [f for f in os.listdir(folder_path) if f.endswith('.txt')]
129
- return text_files
 
 
 
 
 
 
130
 
131
- if __name__ == "__main__":
132
- try:
133
- import jaconv
134
- except ImportError:
135
- print("エラー: 'jaconv' ライブラリがインストールされていません。インストールするには 'pip install jaconv' を実行してください。")
136
- exit()
137
-
138
- text_files = load_text_files(input_folder)
139
- if not text_files:
140
- print(f"フォルダ '{input_folder}' にテキストファイルが見つかりませんでした。")
141
  else:
142
- for filename in text_files:
143
- print(f"処理中のファイル: {filename}")
144
- process_file(filename, input_folder, output_folder)
145
- print("すべてのファイルの処理が完了しました。")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import re
3
+ import argparse
4
+ import configparser
5
  from janome.tokenizer import Tokenizer
6
  import jaconv
7
+ from typing import List, Optional, Dict
8
+ import logging
9
+ import json
10
+ import tkinter as tk
11
+ from tkinter import filedialog, messagebox
12
 
13
+ # ロギングの設定
14
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
15
+ logger = logging.getLogger(__name__)
16
 
17
+ class TextProcessingError(Exception):
18
+ """テキスト処理に関するカスタム例外クラス"""
19
+ pass
20
+
21
+ class FileProcessingError(TextProcessingError):
22
+ """ファイル処理に関するカスタム例外クラス"""
23
+ pass
24
+
25
+ class InvalidOutputFormatError(TextProcessingError):
26
+ """無効な出力フォーマットに関するカスタム例外クラス"""
27
+ pass
28
+
29
+ class TextProcessor:
30
+ def __init__(self, udic_path: Optional[str] = None, remove_symbols: bool = True, convert_kanji: bool = False):
31
+ """
32
+ TextProcessorクラスの初期化
33
+
34
+ Args:
35
+ udic_path (Optional[str]): ユーザー辞書のパス。デフォルトはNone。
36
+ remove_symbols (bool): 記号を削除するかどうか。デフォルトはTrue。
37
+ convert_kanji (bool): 漢字をひらがなに変換するかどうか。デフォルトはFalse。
38
+ """
39
+ self.tokenizer = Tokenizer(udic=udic_path) if udic_path else Tokenizer()
40
+ self.remove_symbols = remove_symbols
41
+ self.convert_kanji = convert_kanji
42
+ logger.info(f"TextProcessor initialized with udic_path: {udic_path}, remove_symbols: {remove_symbols}, convert_kanji: {convert_kanji}")
43
+
44
+ def extract_hiragana(self, text: str) -> str:
45
+ """
46
+ テキストデータからひらがなを抽出します。
 
 
 
47
 
48
+ Args:
49
+ text (str): 処理するテキスト。
50
 
51
+ Returns:
52
+ str: ひらがな抽出後のテキスト。
53
+ """
54
+ hiragana_words: List[str] = []
55
+ for token in self.tokenizer.tokenize(text):
56
+ if token.reading != '*':
57
+ # 読みが存在する場合は、ひらがなに変換
58
+ reading = jaconv.h2z(token.reading, kana=True, ascii=False, digit=False)
59
+ hiragana_words.append(jaconv.kata2hira(reading))
60
+ elif all('\u3041' <= char <= '\u3096' for char in token.surface):
61
+ # ひらがなの場合はそのまま追加
62
+ hiragana_words.append(token.surface)
63
+ elif all('\u4e00' <= char <= '\u9fff' for char in token.surface) and self.convert_kanji:
64
+ # 漢字のみの場合は、変換を試みる
65
+ # (ここに漢字をひらがなに変換するロジックを追加)
66
+ # 今のところ、漢字はそのまま追加
67
+ hiragana_words.append(token.surface)
68
+ elif any('\u3041' <= char <= '\u3096' for char in token.surface) or any('\u4e00' <= char <= '\u9fff' for char in token.surface):
69
+ # ひらがなと漢字が混ざっている場合は、可能な限りひらがな化
70
+ mixed_word = ""
71
+ for char in token.surface:
72
+ if '\u3041' <= char <= '\u3096':
73
+ mixed_word += char
74
+ elif '\u4e00' <= char <= '\u9fff' and self.convert_kanji:
75
+ mixed_word += char
76
+ hiragana_words.append(mixed_word)
77
+ elif token.part_of_speech.startswith('記号') and not self.remove_symbols:
78
+ # 記号を削除しない設定の場合
79
+ hiragana_words.append(token.surface)
80
+
81
+ return "".join(hiragana_words)
82
+
83
+ def preprocess_line(self, text: str, to_lower: bool = False, remove_punctuation: bool = False, remove_numbers: bool = False) -> str:
84
+ """
85
+ テキストデータの前処理を行います(行ごと処理)。
86
+
87
+ Args:
88
+ text (str): 処理するテキスト。
89
+ to_lower (bool): 小文字に変換するかどうか。デフォルトはFalse。
90
+ remove_punctuation (bool): 句読点を削除するかどうか。デフォルトはFalse。
91
+ remove_numbers (bool): 数字を削除するかどうか。デフォルトはFalse。
92
+
93
+ Returns:
94
+ str: 前処理後のテキスト。
95
+ """
96
+ text = text.replace('\r\n', '\n').replace('\r', '\n').strip()
97
+ text = re.sub(r'\s+', ' ', text)
98
+
99
+ if to_lower:
100
+ text = text.lower()
101
+
102
+ if remove_punctuation:
103
+ text = re.sub(r'[!"#$%&\'()*+,-./:;<=>?@\[\\\]^_`{|}~]', '', text)
104
+
105
+ if remove_numbers:
106
+ text = re.sub(r'[0-9]', '', text)
107
+
108
+ katakana_words = []
109
+ for token in self.tokenizer.tokenize(text):
110
+ if token.reading == '*':
111
+ katakana_words.append(token.surface)
112
+ else:
113
+ katakana_words.append(token.reading)
114
+
115
+ return "".join(katakana_words)
116
+
117
+ def read_text_with_bom_removal(filepath: str, encoding: str = 'utf-8') -> str:
118
  """
119
  BOM付きの可能性のあるテキストファイルを読み込み、BOMを取り除いて返します。
120
+
121
+ Args:
122
+ filepath (str): ファイルパス。
123
+ encoding (str): ファイルのエンコーディング。デフォルトは'utf-8'。
124
+
125
+ Returns:
126
+ str: BOMを取り除いたテキストデータ。
127
+
128
+ Raises:
129
+ FileProcessingError: ファイルの読み込みに失敗した場合。
130
  """
131
+ try:
132
+ with open(filepath, 'rb') as f:
133
+ raw_data = f.read()
134
 
135
+ if raw_data.startswith(b'\xef\xbb\xbf'):
136
+ return raw_data[3:].decode(encoding)
137
+ else:
138
+ return raw_data.decode(encoding)
139
+ except FileNotFoundError:
140
+ raise FileProcessingError(f"ファイル '{filepath}' が見つかりません。")
141
+ except UnicodeDecodeError as e:
142
+ raise FileProcessingError(f"ファイル '{filepath}' のデコードに失敗しました: {e}")
143
+ except Exception as e:
144
+ raise FileProcessingError(f"ファイル '{filepath}' の読み込み中に予期しないエラーが発生しました: {e}")
145
 
146
+ def output_comparison_data(filename: str, original_text: str, preprocessed_text: str, hiragana_text: str, output_folder: str, output_format: str = 'tsv') -> None:
147
  """
148
+ 元のテキスト、カタカナ変換後のテキスト、ひらがな抽出後のテキストを行ごとに指定されたフォーマットでファイルに出力します。
149
+ TSVとJSONLの両方を出力するように変更。
150
  """
151
  if not os.path.exists(output_folder):
152
+ # ディレクトリがない場合、作成するか確認
153
+ if messagebox.askyesno("ディレクトリ作成", f"出力ディレクトリ '{output_folder}' が存在しません。作成しますか?"):
154
+ try:
155
+ os.makedirs(output_folder)
156
+ except Exception as e:
157
+ raise FileProcessingError(f"出力ディレクトリ '{output_folder}' の作成に失敗しました: {e}")
158
+ else:
159
+ raise FileProcessingError(f"出力ディレクトリ '{output_folder}' が存在しないため、処理を中止します。")
160
 
161
  base_filename, ext = os.path.splitext(filename)
162
+ output_tsv_filepath = os.path.join(output_folder, f"{base_filename}_comparison.tsv")
163
+ output_jsonl_filepath = os.path.join(output_folder, f"{base_filename}_comparison.jsonl")
164
 
165
  try:
166
+ with open(output_tsv_filepath, 'w', encoding='utf-8', errors='replace') as tsvfile, \
167
+ open(output_jsonl_filepath, 'w', encoding='utf-8', errors='replace') as jsonlfile:
168
+
169
+ tsvfile.write("Original\tKatakana\tHiragana\n")
170
  original_lines = original_text.splitlines()
171
  preprocessed_lines = preprocessed_text.splitlines()
172
  hiragana_lines = hiragana_text.splitlines()
 
176
  original = original_lines[i] if i < len(original_lines) else ""
177
  preprocessed = preprocessed_lines[i] if i < len(preprocessed_lines) else ""
178
  hiragana = hiragana_lines[i] if i < len(hiragana_lines) else ""
 
179
 
180
+ # TSVファイルへの書き込み
181
+ tsvfile.write(f"{original}\t{preprocessed}\t{hiragana}\n")
182
+
183
+ # JSONLファイルへの書き込み
184
+ json_data = {
185
+ "Original": original,
186
+ "Katakana": preprocessed,
187
+ "Hiragana": hiragana
188
+ }
189
+ jsonlfile.write(json.dumps(json_data, ensure_ascii=False) + '\n')
190
+
191
+ logger.info(f"比較データを '{output_tsv_filepath}' に出力しました。")
192
+ logger.info(f"比較データを '{output_jsonl_filepath}' に出力しました。")
193
 
194
  except Exception as e:
195
+ raise FileProcessingError(f"ファイルへの書き込みに失敗しました: {e}")
196
 
197
+ def process_file(filepath: str, output_folder: str, text_processor: TextProcessor, output_format: str) -> None:
198
  """
199
+ 指定されたテキストファイルを読み込み、行ごとに前処理(カタカナ変換)とひらがな抽出を行い、比較データを出力します。
200
+
201
+ Args:
202
+ filepath (str): 処理するファイルのパス。
203
+ output_folder (str): 出力フォルダのパス。
204
+ text_processor (TextProcessor): テキスト処理を行うTextProcessorオブジェクト。
205
+ output_format (str): 出力フォーマット('tsv'または'csv')。
206
+
207
+ Raises:
208
+ FileProcessingError: ファイルの読み込みまたは処理中にエラーが発生した場合。
209
  """
210
+ filename = os.path.basename(filepath)
211
  try:
212
  original_text = read_text_with_bom_removal(filepath)
213
  original_lines = original_text.splitlines()
214
+ preprocessed_lines: List[str] = []
215
+ hiragana_lines: List[str] = []
216
 
217
  for line in original_lines:
218
  if line.strip():
219
+ preprocessed_line = text_processor.preprocess_line(line)
220
+ hiragana_line = text_processor.extract_hiragana(line)
221
  preprocessed_lines.append(preprocessed_line)
222
  hiragana_lines.append(hiragana_line)
223
  else:
224
+ preprocessed_lines.append("") # 空行を保持
225
+ hiragana_lines.append("") # 空行を保持
226
 
227
+ output_comparison_data(filename, original_text, "\n".join(preprocessed_lines), "\n".join(hiragana_lines), output_folder, output_format)
228
 
229
+ except FileProcessingError as e:
230
+ raise e
231
  except Exception as e:
232
+ raise FileProcessingError(f"ファイル '{filepath}' の処理中にエラーが発生しました: {e}")
233
 
234
+ def select_files():
235
+ """ファイルを選択するダイアログを表示します。"""
236
+ root = tk.Tk()
237
+ root.withdraw() # メインウィンドウを非表示
238
+ file_paths = filedialog.askopenfilenames(title="処理するテキストファイルを選択してください", filetypes=[("Text files", "*.txt")])
239
+ return list(file_paths)
240
+
241
+ def select_output_folder():
242
+ """出力フォルダを選択するダイアログを表示します。"""
243
+ root = tk.Tk()
244
+ root.withdraw() # メインウィンドウを非表示
245
+ folder_path = filedialog.askdirectory(title="出力フォルダを選択してください")
246
+ return folder_path
247
+
248
+ def main():
249
  """
250
+ メイン関数。テキストファイルの前処理とひらがな抽出を行います。
251
  """
252
+ parser = argparse.ArgumentParser(description='テキストファイルの前処理とひらがな抽出を行います。')
253
+ parser.add_argument('--config', type=str, help='設定ファイルのパス')
254
+ parser.add_argument('--udic_path', type=str, help='udicのパス')
255
+ parser.add_argument('--file_extension', type=str, default='.txt', help='処理するファイルの拡張子')
256
+ parser.add_argument('--output_format', type=str, default='tsv', choices=['tsv', 'csv'], help='出力フォーマット(tsvまたはcsv)')
257
+ parser.add_argument('--remove_symbols', action='store_true', help='記号を削除する')
258
+ parser.add_argument('--convert_kanji', action='store_true', help='漢字をひらがなに変換する')
259
+ args = parser.parse_args()
260
 
261
+ config = configparser.ConfigParser()
262
+ if args.config and os.path.exists(args.config):
263
+ config.read(args.config)
264
+ output_folder = config.get('Paths', 'output_folder', fallback=None)
265
+ udic_path = config.get('Paths', 'udic_path', fallback=args.udic_path)
266
+ file_extension = config.get('Settings', 'file_extension', fallback=args.file_extension)
267
+ output_format = config.get('Settings', 'output_format', fallback=args.output_format)
268
+ remove_symbols = config.getboolean('Settings', 'remove_symbols', fallback=not args.remove_symbols)
269
+ convert_kanji = config.getboolean('Settings', 'convert_kanji', fallback=args.convert_kanji)
 
270
  else:
271
+ output_folder = None
272
+ udic_path = args.udic_path
273
+ file_extension = args.file_extension
274
+ output_format = args.output_format
275
+ remove_symbols = not args.remove_symbols
276
+ convert_kanji = args.convert_kanji
277
+
278
+ # ファイル選択ダイアログを表示
279
+ file_paths = select_files()
280
+ if not file_paths:
281
+ logger.info("ファイルが選択されなかったため、処理を中止します。")
282
+ return
283
+
284
+ # 出力フォルダ選択ダイアログを表示
285
+ if output_folder is None:
286
+ output_folder = select_output_folder()
287
+ if not output_folder:
288
+ logger.info("出力フォルダが選択されなかったため、処理を中止します。")
289
+ return
290
+
291
+ # パスの正規化
292
+ output_folder = os.path.normpath(output_folder)
293
+
294
+ text_processor = TextProcessor(udic_path, remove_symbols, convert_kanji)
295
+
296
+ try:
297
+ total_files = len(file_paths)
298
+ for i, filepath in enumerate(file_paths):
299
+ logger.info(f"処理中のファイル ({i+1}/{total_files}): {filepath}")
300
+ try:
301
+ process_file(filepath, output_folder, text_processor, output_format)
302
+ except FileProcessingError as e:
303
+ logger.error(f"ファイル '{filepath}' の処理中にエラーが発生しました: {e}")
304
+ logger.info("すべてのファイルの処理が完了しました。")
305
+ except FileProcessingError as e:
306
+ logger.error(f"ファイルの処理中にエラーが発生しました: {e}")
307
+
308
+ if __name__ == "__main__":
309
+ main()