Coverage for pythonutils / csvutil.py: 100%

48 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-21 13:48 +0900

1# -*- config: utf8 -*- 

2'''csvutil module. 

3 

4Copyright ycookjp 

5https://github.com/ycookjp/ 

6 

7''' 

8 

9from io import TextIOBase 

10 

11def _delete_line_break(strdata: str) -> str: 

12 '''文字列の最後の改行コードを除去する。 

13  

14 Arguments: 

15 strdata (str): 文字列 

16 

17 Returns: 

18 引数で指定された文字列の最後に改行コードが存在した場合は、その改行 

19 コードを除去した文字列を返す。そうでない場合は、引数で指定された文字列を 

20 そのまま返す。 

21  

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] 

27 

28 return strdata 

29 

30def _trim_double_quote(strdata: str): 

31 '''文字列の先頭と終わりのダブルクォートを除去する。 

32  

33 Arguments: 

34 strdata (str): 文字列 

35  

36 Returns: 

37 引数で指定された文字列の先頭と最後の文字が共にダブルクォートの場合は 

38 そのダブルクォートを除去した文字列を返す。またその場合に連続した2つの 

39 ダブルクォートは1つのダブルクォートに置換する。 

40 

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('""', '"') 

46 

47 return strdata 

48 

49def read_csv(istream: TextIOBase) -> list: 

50 '''ストリームからCSVの1行のデータをlistで反復して返す。 

51  

52 CSV形式の文字列からCSVの項目を要素とするlistを生成して返却する処理は 

53 以下のとおりである。 

54  

55 1. 「"」が見つかったら次の「"」が見つかるまでコンマや改行を含めて読み込んだ 

56 文字列を現在処理中のlist項目の文字列に追加する。 

57 2. カンマが見つかったら、現在処理中のList項目の文字列をlistに追加して、 

58 次のList項目の文字列追加処理を開始する。その際追加されたlist項目の文字列の 

59 先頭と最後が「"」である場合は、最初と最後の「"」を除去し、連続する2つの 

60 「"」は1つの「"」に変換する。 

61 3. 改行またはストリームの終わりに達したら、現在処理中のlist項目の文字列から 

62 最後の改行コードを除いてlistに追加してそのlistを返す。なお、追加された 

63 list項目の文字列の先頭と最後が「"」の場合の扱いは、カンマが見つかった場合 

64 と同様である。 

65 

66 Args: 

67 istream (TextIOBase): 入力ストリーム 

68  

69 Examples: 

70 ストリームを読み込みCSVの1行のデータを配列にして返す処理の例 

71 

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) 

80 

81 ''' 

82 in_dquote: bool = False 

83 csvcol = '' 

84 rowdata = [] 

85 

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