Coverage for pythonutils / csvutil.py: 100%
48 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-21 13:48 +0900
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-21 13:48 +0900
1# -*- config: utf8 -*-
2'''csvutil module.
4Copyright ycookjp
5https://github.com/ycookjp/
7'''
9from io import TextIOBase
11def _delete_line_break(strdata: str) -> str:
12 '''文字列の最後の改行コードを除去する。
14 Arguments:
15 strdata (str): 文字列
17 Returns:
18 引数で指定された文字列の最後に改行コードが存在した場合は、その改行
19 コードを除去した文字列を返す。そうでない場合は、引数で指定された文字列を
20 そのまま返す。
22 '''
23 if strdata[len(strdata)-2:len(strdata)] == '\r\n':
24 strdata = strdata[:len(strdata) - 2]
25 elif strdata[len(strdata)-1] == '\n':
26 strdata = strdata[:len(strdata)-1]
28 return strdata
30def _trim_double_quote(strdata: str):
31 '''文字列の先頭と終わりのダブルクォートを除去する。
33 Arguments:
34 strdata (str): 文字列
36 Returns:
37 引数で指定された文字列の先頭と最後の文字が共にダブルクォートの場合は
38 そのダブルクォートを除去した文字列を返す。またその場合に連続した2つの
39 ダブルクォートは1つのダブルクォートに置換する。
41 '''
42 if (len(strdata) > 1 and strdata[0] == '"'
43 and strdata[len(strdata) - 1] == '"'):
44 strdata = strdata[1:len(strdata)-1]
45 strdata = strdata.replace('""', '"')
47 return strdata
49def read_csv(istream: TextIOBase) -> list:
50 '''ストリームからCSVの1行のデータをlistで反復して返す。
52 CSV形式の文字列からCSVの項目を要素とするlistを生成して返却する処理は
53 以下のとおりである。
55 1. 「"」が見つかったら次の「"」が見つかるまでコンマや改行を含めて読み込んだ
56 文字列を現在処理中のlist項目の文字列に追加する。
57 2. カンマが見つかったら、現在処理中のList項目の文字列をlistに追加して、
58 次のList項目の文字列追加処理を開始する。その際追加されたlist項目の文字列の
59 先頭と最後が「"」である場合は、最初と最後の「"」を除去し、連続する2つの
60 「"」は1つの「"」に変換する。
61 3. 改行またはストリームの終わりに達したら、現在処理中のlist項目の文字列から
62 最後の改行コードを除いてlistに追加してそのlistを返す。なお、追加された
63 list項目の文字列の先頭と最後が「"」の場合の扱いは、カンマが見つかった場合
64 と同様である。
66 Args:
67 istream (TextIOBase): 入力ストリーム
69 Examples:
70 ストリームを読み込みCSVの1行のデータを配列にして返す処理の例
72 from pythonutils import csvutil
73 ...
74 with open('/path/to/sample.csv', 'r', encoding=''utf-8) as f:
75 for rowdata in csvutil.read_csv(f):
76 line = ''
77 for celldata in rowdata:
78 line = line + (',' if len(line) > 0 else '') + celldata
79 print(line)
81 '''
82 in_dquote: bool = False
83 csvcol = ''
84 rowdata = []
86 while True:
87 # ストリームから1行読み込む
88 line: str = istream.readline()
89 # ストリームの終わりに達したら処理を終了する
90 if not line:
91 break
92 index = 0
93 # 1行の文字列を順に調べる
94 while index < len(line):
95 # ダブルクォートの中である場合
96 if in_dquote:
97 # 次のダブルクォートの出現位置を取得
98 dqidx = line.find('"', index)
99 # 次のダブルクォートが見つからない場合は改行を含む行末までの
100 # 文字列をセルの文字に追加して、次の行を読み込む
101 if dqidx < 0:
102 csvcol = csvcol + line[index:]
103 index = len(line)
104 # 次のダブルクォートの文字が見つかったらそこまでの文字列をセルの
105 # 文字に追加して、それ以降の文字を処理する
106 else:
107 csvcol = csvcol + line[index:dqidx+1]
108 index = dqidx + 1
109 in_dquote = False
110 continue
111 else:
112 # 次のダブルクォート、カンマの出現位置を取得
113 dqidx = line.find('"', index)
114 cmidx = line.find(',', index)
115 # ダブルクォートの前にコンマが存在しない場合
116 # ダブルクォートまでをセルの文字列に追加する
117 if dqidx >= 0 and (cmidx < 0 or dqidx < cmidx):
118 csvcol = csvcol + line[index:dqidx+1]
119 in_dquote = True
120 index = dqidx + 1
121 # コンマの前にダブルクォートが存在しない場合
122 # コンマの前までの文字列をセルの文字列に追加し、次のセルの処理を開始
123 elif cmidx >= 0:
124 csvcol = csvcol + line[index:cmidx]
125 rowdata.append(_trim_double_quote(csvcol))
126 csvcol = ''
127 index = cmidx + 1
128 # コンマもダブルクォートも存在しない場合
129 # 行末までの文字をセルの文字列に追加し、1行分のCSVデータを返す
130 else:
131 csvcol = _delete_line_break(csvcol + line[index:])
132 rowdata.append(_trim_double_quote(csvcol))
133 yield rowdata
134 csvcol = ''
135 rowdata = []
136 break