From 479e6392ab28f319425a2f93fcdecf7143989972 Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Tue, 26 Feb 2019 22:15:18 -0800 Subject: [PATCH 01/55] Class to read OpenDocument Tables This is primarly intended for OpenDocument spreadsheets like what is generated by LibreOffice calc but will also work with LibreOffice Writer. --- ci/deps/travis-36-cov.yaml | 1 + pandas/io/excel/_base.py | 2 + pandas/io/excel/_odfreader.py | 165 ++++++++++++++++++++ pandas/tests/io/data/blank-row-repeat.ods | Bin 0 -> 10894 bytes pandas/tests/io/data/datatypes.ods | Bin 0 -> 10608 bytes pandas/tests/io/data/headers.ods | Bin 0 -> 8325 bytes pandas/tests/io/data/invalid_value_type.ods | Bin 0 -> 8502 bytes pandas/tests/io/data/lowerdiagonal.ods | Bin 0 -> 7528 bytes pandas/tests/io/data/runlengthencoding.ods | Bin 0 -> 7898 bytes pandas/tests/io/data/writertable.odt | Bin 0 -> 10313 bytes pandas/tests/io/test_excel.py | 132 ++++++++++++++++ 11 files changed, 300 insertions(+) create mode 100644 pandas/io/excel/_odfreader.py create mode 100644 pandas/tests/io/data/blank-row-repeat.ods create mode 100644 pandas/tests/io/data/datatypes.ods create mode 100644 pandas/tests/io/data/headers.ods create mode 100644 pandas/tests/io/data/invalid_value_type.ods create mode 100644 pandas/tests/io/data/lowerdiagonal.ods create mode 100644 pandas/tests/io/data/runlengthencoding.ods create mode 100644 pandas/tests/io/data/writertable.odt diff --git a/ci/deps/travis-36-cov.yaml b/ci/deps/travis-36-cov.yaml index 606b5bac8306d..69d4a499e41ad 100644 --- a/ci/deps/travis-36-cov.yaml +++ b/ci/deps/travis-36-cov.yaml @@ -15,6 +15,7 @@ dependencies: - nomkl - numexpr - numpy=1.15.* + - odfpy - openpyxl - pandas-gbq # https://github.com/pydata/pandas-gbq/issues/271 diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index c0678575fd6f0..828343be81203 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -780,9 +780,11 @@ class ExcelFile: """ from pandas.io.excel._xlrd import _XlrdReader + from pandas.io.excel._odfreader import _ODFReader _engines = { 'xlrd': _XlrdReader, + 'odf': _ODFReader, } def __init__(self, io, engine=None): diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py new file mode 100644 index 0000000000000..ce63a3240dba0 --- /dev/null +++ b/pandas/io/excel/_odfreader.py @@ -0,0 +1,165 @@ +import pandas +from pandas.io.parsers import TextParser + + +class _ODFReader(object): + """Read tables out of OpenDocument formatted files + + Parameters + ---------- + filepath_or_stream: string, path to be parsed or + an open readable stream. + """ + def __init__(self, filepath_or_stream): + try: + from odf.opendocument import load as document_load + from odf.table import Table + except ImportError: + raise ImportError("Install odfpy for OpenDocument support") + + self.filepath_or_stream = None + self.document = None + self.tables = None + self.filepath_or_stream = filepath_or_stream + self.document = document_load(filepath_or_stream) + self.tables = self.document.getElementsByType(Table) + + @property + def sheet_names(self): + """Return table names is the document""" + from odf.namespaces import TABLENS + return [t.attributes[(TABLENS, 'name')] for t in self.tables] + + def get_sheet_by_index(self, index): + return self.__get_table(self.tables[index]) + + def get_sheet_by_name(self, name): + i = self.sheet_names.index(name) + return self.__get_table(self.tables[i]) + + def get_sheet(self, name): + """Given a sheet name or index, return the root ODF Table node + """ + if isinstance(name, str): + return self.get_sheet_by_name(name) + elif isinstance(name, int): + return self.get_sheet_by_index(name) + else: + raise ValueError( + 'Unrecognized sheet identifier type {}. Please use' + 'a string or integer'.format(type(name))) + + def parse(self, sheet_name=0, **kwds): + data = self.get_sheet(sheet_name) + parser = TextParser(data, **kwds) + return parser.read() + + def __get_table(self, sheet): + """Parse an ODF Table into a list of lists + """ + from odf.table import TableCell, TableRow + + sheet_rows = sheet.getElementsByType(TableRow) + table = [] + empty_rows = 0 + max_row_len = 0 + for i, sheet_row in enumerate(sheet_rows): + sheet_cells = sheet_row.getElementsByType(TableCell) + empty_cells = 0 + table_row = [] + for j, sheet_cell in enumerate(sheet_cells): + value = self.__get_cell_value(sheet_cell) + column_repeat = self.__get_cell_repeat(sheet_cell) + + if len(sheet_cell.childNodes) == 0: + empty_cells += column_repeat + else: + if empty_cells > 0: + table_row.extend([None] * empty_cells) + empty_cells = 0 + table_row.extend([value] * column_repeat) + + if max_row_len < len(table_row): + max_row_len = len(table_row) + + row_repeat = self.__get_row_repeat(sheet_row) + if self.__is_empty_row(sheet_row): + empty_rows += row_repeat + else: + if empty_rows > 0: + # add blank rows to our table + table.extend([[None]] * empty_rows) + empty_rows = 0 + table.append(table_row) + + # Make our table square + for row in table: + if len(row) < max_row_len: + row.extend([None] * (max_row_len - len(row))) + + return table + + def __get_row_repeat(self, row): + """Return number of times this row was repeated + + Repeating an empty row appeared to be a common way + of representing sparse rows in the table. + """ + from odf.namespaces import TABLENS + repeat = row.attributes.get((TABLENS, 'number-rows-repeated')) + if repeat is None: + return 1 + return int(repeat) + + def __get_cell_repeat(self, cell): + from odf.namespaces import TABLENS + repeat = cell.attributes.get((TABLENS, 'number-columns-repeated')) + if repeat is None: + return 1 + return int(repeat) + + def __is_empty_row(self, row): + """Helper function to find empty rows + """ + for column in row.childNodes: + if len(column.childNodes) > 0: + return False + + return True + + def __get_cell_value(self, cell): + from odf.namespaces import OFFICENS + cell_type = cell.attributes.get((OFFICENS, 'value-type')) + if cell_type == 'boolean': + cell_value = cell.attributes.get((OFFICENS, 'boolean')) + return bool(cell_value) + elif cell_type in ('float', 'percentage'): + cell_value = cell.attributes.get((OFFICENS, 'value')) + return float(cell_value) + elif cell_type == 'string': + return str(cell) + elif cell_type == 'currency': + cell_value = cell.attributes.get((OFFICENS, 'value')) + return float(cell_value) + elif cell_type == 'date': + cell_value = cell.attributes.get((OFFICENS, 'date-value')) + return pandas.Timestamp(cell_value) + elif cell_type == 'time': + cell_value = cell.attributes.get((OFFICENS, 'time-value')) + return(pandas_isoduration_compatibility(cell_value)) + elif cell_type is None: + return None + else: + raise ValueError('Unrecognized type {}'.format(cell_type)) + + +def pandas_isoduration_compatibility(duration): + """Libreoffice returns durations without any day attributes + + For example PT3H45M0S. The current pandas Timedelta + parse requires the presence of a day component. + Workaround for https://github.com/pandas-dev/pandas/issues/25422 + """ + if duration.startswith('PT'): + duration = 'P0DT' + duration[2:] + return pandas.Timedelta(duration) diff --git a/pandas/tests/io/data/blank-row-repeat.ods b/pandas/tests/io/data/blank-row-repeat.ods new file mode 100644 index 0000000000000000000000000000000000000000..e6712854eb5cd7dedaf0aa159903536e68585a6d GIT binary patch literal 10894 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 OmkAsIa7O)j_5T2;#F*#+ literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/headers.ods b/pandas/tests/io/data/headers.ods new file mode 100644 index 0000000000000000000000000000000000000000..753c24762a9ec2940bd282476d1ece2cd4252281 GIT binary patch literal 8325 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 literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/invalid_value_type.ods b/pandas/tests/io/data/invalid_value_type.ods new file mode 100644 index 0000000000000000000000000000000000000000..75a7a40b25d79366d8f99fd4945e8e6e7bbffae9 GIT binary patch literal 8502 zcmb`Mby$?!_Q!`9q(wqPKtNg=kyL4rp#=u%p<@^j29Q)*K|(sETT+@sH&TL>G>DWS z-SHca_a4E^ImdJF?|q(ko_GHF?6vn=d#$~{OA&;EiU~M-ka9HSfB*Q`4<3LVAZ7(K zHZ!rafxykIV76TBswx-&lnqLIt+UteDHZ?;bpZtc`29!m57H*H<^>Y@B_xP}ff3XQ z0k*RFPSpsq*J7vo2UN04sMc22cGi|gFuVUpCfv#jY7K!ILH|o8$t8B4nWzu>)tUe7 z8RAQbW|j~WBisMdK{x*#C~5|`gjoLz{EvNf+1$_8^VjG8b1Yp(G=^G1;6}f?nm-dS ziz8b&1a4>h-&=WE#9P}~nb;WF{t}fCfDAwfU?XguOMalG7C_xZ#gxC&io=6_zO9JvgyC2W1rxcsTNZ8i)8cRh61qBs{<9h8s&oNKY2%91#d}Y>|e9Vhi zQh?)H+@zOn`d;u8Z7mSlOa$RIRNR_5U(ja)2m_(ooflUMh(gm(9Ua}~oEe8nSx#@r z8k$ku?uq8CK@WX>B8j8&shpZ~|7hBOKm%uNCr`0Ir4i)3(2br%k6e+#C%J|n>gn|* z@&mMb{Ay4!uz7PUy^|aU%OFU!uv__FYE7TBfTyX%{ev{Dn$U4sJ+nZeh*NAeCPh0f) zZ<}I$Jj;ErE5{Em=D+RRX#}#7r?i$@ujb92h7sw6Us5fuYYM2&uEq_>&wdJt zE4KHUDN9HhF4w{Bt|a*s{ajOhKq7i4W}P~#AhMnh<8HfMDNmhSFsq(sipaOhvf=t0 zOh;(P$Hz0U#}yIJ#7$yiqi2lawj$UH`f4JdukKPgIRB>geqg3vuvV_TR2V*ISWoz} zBt8pr->NllbM-2g3(TFGXcP&9?3r-4`5c>vA2OzuF12OaaMo=9<4r*_-O|)vWL74z zu^m=bff15;y`1JnAeq00`TReC>&T}lHMEt*9BTB4)8`MIETHOyvID|nSsU6D z&j)}m>`D8p3Vo@Q)tnsyp5EK^)v2FU@Pom7&t!WKD_Z#PFGf4&(Xct+sh7-^l zm?sCG*Eb<=qwZm0b|BD(v7^E~p^nE#9mu{BgFjId7^+Ni*%;nLeTV<^_mJ<}4HD;;3W{JIg|ti0IJo_EQN zPh&QP6n>+rAK-f;jonflh>k%SAZoV#*@cY1dj`W8tOQ?940hb5y5~PGkd;`32`N>s z_ARA)?JgXEA=}{v_av{B2Q_m?Y%1~BC#}r1>Q(o9CJX7U-45&-o_K+sKBqHPU9hU< zIq4E^>a=R$WY^!kK6xulFOF+fVW~>c@pS%#t>=_eXY+VsONJ!>C$BEu&Qn6 z5%sp!$92s?4XIQ|^DEho>t6PQ5m?oyEzDeZaT^VlIm-0WQV4(Y7V`+L(snRyN#7OW zmC-SjkfTv{sfqvA7sbm@HFBQ?(?t|&l(^x`EQ zqp!j0GsVY|sB$360_W%Y&I9fwOa59rlmRO?z@;uZn5)kLLCLc>c%>GeZfjf zPE(+{Yt4!}siP=ak^c0ReA4+Q)o<1_y9lNu^UCHHgjG(q-mTqO|JG{t4C&&2NHCf7 z%|&=18L=PG&-Q~69P&pE{*%2B5DE&4yh5d^b48J<`+u>Ob3y!Bis}BbI zNd@feGJr_6Vg?S%ggUTNL5xpO8ijfMbg8*Yqnw?Gk8}MBX(x0V6m!*6axOW1zeFs5 zB7vV;d#G8POcP8HGtW<&&CT2#E?6diHH;AT&K3cgsDX@eslr}ul9N4YyM{Bx$612~ z2stPOhjfTrTaan{6B=@CNN~Xhk=vu*@g&nvCo^@V%=7T&3lB9F0X@%B`j>{cA z*<5%l@s_S4YA-+Vz7;hg_YrN!kD2v?M68g`vEb;&6%41CpgMX>|3Wsvf@8`jKqxE4Grv*$HB0(>Yt;AyrhXmV z2c;v_qvBXo*7l{}q)cs2vs5+o@1~uO=2P(!D-607ZCt(RZf_{R9}D6h#D;i(cefeB z;f~fuf0g=x_3jt|Ds?FB|u@)q< zeV|FN>gV)DgSXmAHkK7ilS1(X?dmEQ$G2YAN7QKN)@ZxlPKeJJ9aQ%SCUhLK3Di(S zDGx%L+sv~Qtbm@F#qXZ*HIBSGm_l1kO#eKgePd zKY}pE2MdOhT9j!p*q=bJeA82r5+YB!1q{ZJ9NbCMkUF57>sX2}4ea?E$zkPD;}gx?0Hb(y6o6)~ggOqZ zb$-TifVbeHw6NbUn8EDCO{)~T0Z_yHs^O9PG_;66Mn_+}SywvGgMuLqKL$ySeXem- zNKVAzC=QPzD1raL#jgyYC=W!&)u&c^rKp^?f9epx;uz?nD;;ti?8~@t%SIxKm^1sN z0bt))EBpjdE5f)GD>}LEb|X<>b2IA{ZSAlo8PwL>>eT~kcsKtAcAQ&bYzb*KBAOE( z3t4idS^_eALjvk1uQk4u%nJX&v_JvjB;;Qz-U&d87SqtwmH74*W0jR-pLPK@@`i?s zYHW$=TH~AcdRGcrbov5iUJbQQ68a$yhLg(9!B-rQ^*h~;O^b8|#j?LVv9Amx*WKr| zv5wyaa^0m%R$=9nZ{3$$5W|QkEAK5@gwRqEQ99%0`9*)D;@H$3Ot{B%0#->+>`Ha< z6A<=E4rW{FF1+3tPgXi0tmq?d-ZL8<#7T;b0l#6`B&{Y5Pvd;dsKJMV^3a~{o_sP!;7b)MY?3zj?U)U<_fg)Q(sFryEe&S1 zRs$%{tdJ-CGmx~EA1}GdP3`h{=(dyB)F$_7S}@TB_eyVt_!^QJhGcd(G2UnpCqCVA=b1sI6K2ntKTb|~s@ zC+HLDAf?>AO^)}a^%Z00vy!2(2V;`%Eo`+o{3qVPHl7f@xAm{zl^}`ECshzu61a14 zy#uCu?>f0qnpg^p5t6@NIcZV}h)cU2?Lln1I zOT|~1HIvFGkqc?q+nC$!`^w(X+Py)uNLS$MG0MdZ@ujz+1&Rg$Y|sJ#n27RY3wMMX z{cV02*RZrgTmnyObg47z3XnpX-dmD67kcJu?hc}f_3Pinib*z14r5d-j^7&kCM{EB z_&9rJP@b1Tnr5S7cHGv%)1QOc8QaWP(tO97c&>vKL!IZAY$maC!)9}M{Cc)=>wSw`^Q*Vf`ZJzPR5UZu zGRcDI?m&**R^9x$U!Ruyz6;3fSIk}4k$tpA=EpEuBxFU!m5wgUeuE{t9p|IfC(uJP zU%8SzN|aSwiO``RK&NpdqJa(yH&S4vvGT>#<+-Pb8AKE1SJMEoXMdTrq0(kKG}0 z68W-~7Y_*^#4OZ5bjrzl0F!--iwb#S#LLoLlXfkdX5Pi&>Ssa)3{cn6HBYTJ{d5~f z7w%r9cq6S(C`bA$PpUSN?66Md3n%h-(FIl6RAU|T`K`enQG2#Xu@ww8`%z&Wh3z9! zig-fauSQEoa$PV-F*_Z)G&^R9I+H;4jT(gaMP#Oj4(pyhVAFoC(!*D?ONMUZ684SG zO6u@_jmz_I5{H}?5Z+UbP?g+{U2j=aE|UL{{s^STupA(zO`M zUNL+x>e|v|K_`HxElB~IW+m?*+RtJ>;-zj#ffE~3Jeu{2B9ro@aegv=Xu{fjqf*T# zl4hPJEo#pvm@EEGd|1Mhr*5L?q|)frSHRu=@fiW|#)L*NM{chqB)7NjS}r0)*Hy2=_vVJX6HSzlr1Tz9gg zWEQ*CcbA{nGHJhN0_c-TuGOI@@%8|K1_BRbRiA;}m#&Sl{SUplP;*4wXy)H?!Y3E!} zQkdI=HJ8r{E8e6_ce4uSu63%=2s?Be5-%r_JM@v^%H|e+~nQrtJgndK6udCT$RWfQmfS5Wa)_kkBguU7G z?x+qYd&}I@lF|r_v~0A+YWl?MK?%t_a_dFKz9WgvZ(T#~7&jR2h_#pEn6AekgE*U) z6%UVG{exRx1kgWv=&a_Y9$pi}Td=)-fMzd6)HbSDGdMJR@^oq6omi_JYf|dV=q}dn zJJ#!-kI1lf_D(uoz>+s*Qqo<6A~oq|GuiYes#wzdE1z2mDzPOu4g}$zzVDmSJ*!uj z_YuxiOBGW)OMMu`4Epy9^^aSx@I8y1b;JQY5^?W3lYP%Y;Q!{aS;I{JIFw(~lvu`I zvz?y{nbFtNo7#z5|KcHet&g*!M-=+QkQX@20IS9xVcg}0ZNxVShi0|PM;(ZIk! zNlA&FgM-2wA;<1GeozF=QMvhbyCHy0vb{yWeiM)t#O? z#NF0ts;gt1TAU$KezLrxDwSjmL;|-)00Z4S98U5Dnaa&pvjw(^Yb#A+7!{ta1Ri1;zdv|@6_K;A z|LpGozxQ{3(hts4f7Z{*FHmJJpXqkr_z=@(;j;S&1!UeZ73U))_n2$kUH*tytO`l}JXr`G@M zDt*ri2;Txy-9r!~T<9?&fM-3XOB2G6Tzye5J`cn|2mGWS|CNcCCoYT&QHbb4pBGUP zxIYc*9|m5OP|q6}c^-FBQ2i@Qf1odFgXhttSctj*p%nfa?V|K_o`#Ntp#334{Tl6} zv~-?kkDy%?ntqLV@hWWmuG5w;objKoBLQOH#Te1nKS$$6@FgngIm{R63;_>28n`=`KY^K)PE*5STN( z-{q&iN4?I^v*vo{c_!Aq_g;JNd&O2)LPIA30I&dn7|Kyan4RzoP5=OKbs@h3*n{nX z5HBa7g_DyV*vbL|c68u!cX-6 zqiZO5>8Jp})pZMrscr3MZ|PtGwsYlze1GJ0asWM7SAB?&Lym*|3%-K<15M<;1$iN` z(2(!3Nz6dxO;24}Tb7lTRX{*MLPA1TR#r(#Nmp0b(9qD*($dz}76O6z`uYY11%-u$ z#m2^_rlw|PWfd0}S5;LtHa2#4b`A~>PE1U|;qc|<<*lu)qobqC%gd`)5D2Fz!7=~< z6;Txas+^4!_vI1tN5n#nhE4Em8yTI13ajIL>E1`_muBmGt~6iY)d`O??bw@L z9Ew)?74h@!7Q3-GZ%%MzBW#iZ%x!NxhM@NeqF;)J=(@Jr&048lOq*%Zq|6H7q4ehQ zjX;~-BOp61yYw)zsm@{Ep4a(^h}LDwJ~@Kluk+4hDxs!- zAFkP=$U&Hq|JcvWtjE>tA!o4BtSS9Kwauo7Pn~dHq2f~Ojp}V`2ct2h`Tsu)V)E!( zM*PjGL>)~ObK0@Q@F#KnMiv0CMH@w71(a7;{Mx zB7x4A@9*!L$J*6dSb9*_8-U>Uyxo0u(Ab5UcMsNiVz`~#{4f&BWU^>)xw(VBu-=Oq zZfT`V^Ixm{&j%RGbSJL~O5P`UTHK1B z_nG9w#6TnDXd#*ucb6_&yD2q@#$=`W77ABFV@UNZyZ+^Jw8kZOm4KpbnLV?DOH68d zrartgG!$2qg5%X$uO4KUPg~*}Hwm2|ZX9QCY+rzIU`JA#f1UR?hUWCeN!B*9du{!(~kqO>}G z8e93=fD{)pAa{u_0vmZ*N?q|-*71$WrCuq&h991zjt|_x`WrQ zc59-lTs-j%?WNWmsQIkYj0p)L#(QZB7A*Q#2tnGEJ<0v&xk2;YANB4P3f*znsH|Tg z4zRTsw51qqGmAtN+kl%|Jo2nZWO;UK2ZhxG>rV3<*p_;5s$k#F$1mtJc;cAYBm1nL zcy%VYQ4msp4%r+4G)w8_p40B#Jtj-yD7Od~smgG{;<5S0RQZP0!q4ti_+DSuiJx(+ zmoYK+8b~khMy2hWXa$nW*);TnV3u*{E3KWc&&plXEN@{)fwRnf?`zwd?Ckj!Rd4yd zr9|MXD_z5~u(xZULwY4lDgfZmynu`31uI7f$dz+M(n6QMk}F(*u=PaGdOB&&eBFZi zCL3$WBNywPJDW8VY!5gY`Di*&B*ibGWS?X*+3(6z@RNN&ZJoXN(rHQdWpS34EQg9^ z2v`}$mrU)??a$(7h=z4!(ARK6jQ2Qm6iF)| zC2uvD3$ITzRI+DTms(O&HR~V05O_#rx7*%Y`!o=nziv96!zkpTyZO9`jvcj)T_sDA zV|t(ZQs7O7*aB};p3V_{N^~v>uAI_)K;;d;wl?_4Ztl!zpRYu|Lp^;eq@halP1bl& z0DwYyqHi{+ZkiGgcLh`_k(b^7J~JTCBDh6lO7p24)+<|F-oa?ciQ@8EriLc>6qKY` z4y_Ki)%FX`q`ZYZ9wjI|HEE~)RAL&riiW9W=;&dd zM$mM*b-Ka$L3&u#{1V8$023$?V$Ip6#d~}vNgAh_q?*Y7`g9SjBI;li8@lk$!oz4? zE^kAYWjsm4j^6WmUqt6+kEtWl&%16)`rU7_;l8aEx>3e(=cMqoSVb^{$AA<(i)nN3 zlX}3Sdnh*O=84!>QugIGrw@aCBQoVGW1!GM?ekU_sEZpjl@1kK9x3a9`z}6 zyRP99Yc4Z;ugm0kY->`YeSUjnQ-tIC4e5;n{AC1e;vojNWG}plF>pRNnsa{ao0%U~ z%WG=rlhpeML+|ij@Yx*>@#V+h`4=8q;-zX+aqEGd8~{Ue9D1^M5q^j+M( zXMA8VW#sORtLyo?bE5en#kt6WB3g9bg7A&E^#Vl!PIJK~tFxS|U-8go73)yO=UT#a z-lQ_FFlx8;X_YB;MBrz1r$9wNg{Ca?$f`*bRr{&L6Wo-mu1wfCm|WV2^=Y zW|<#F`7ZyQB!5Rf8aJ9y)UMG3!vSSt!ho-)h8N%L?6HlpzypV2RA*bSFmI3&y0Q}` zVfQId!aEeha>ZBx&>626Ish<@ z4E6uaSpP))K#0W^U4_T0M5b^P2Jc;vKHHu88mIyYtG$#D$3{tTKOxpSgT8{q&FLWl3wyRenR$QXw zn(lSGRlG6b}+ZG3+8O=a95a@fZZ@IEEIo8cSv#V|Ifa@Gs#za_A%bO2N z0N4{?!4lEb2PlfOd_1A6=V(DjkBAo=UGT|EGL{X}Iefpogn1B!#yk@jQ<^nVn;*Xb zBW{&EndnNcoZH7CKitoXEt0mo^GU6Xa(-5P?hMrBcs0);8b0PJ3BvY>(ZPjf8?26ju|7F=DFHF3+f9 z=t@O5VY>p@G0)B)3!SfiQg3aNSe=AhabmFvTrP?oiW|RWr(Au!~C2wJ*hJs_ZEpfNU`d44r zI%yd$=A^QfRB#wTc>RI+4vNf_RI4~_>`pTzY9EXjax$g*0}@vlV{)y!qGJ}9y5|)P z$~yO*gi@(Fhi{L(>9VQCIWFN9a&M?w)Xs?!dP`E=^`Jq>Ea+y6)$`Sv{0f!W7kAq_ zdfxPyU6a}MV|1&SvTlqWAG}1MMjM4YvUWzY1=#dk$pK(3Ee%72U zQ2aE>YP>W`2tl4=>J;`6&qf)lhHJ{(_t~!nl}M2px;C`K&HPl7req+^ z%cwAU6rem8U*^QdGq&NZjW#Mn8zPg|=i8iL!UD9kd)U2T7bqC@r}#PVlIwcd>lSKv z%7-f=UPNC=oySm)W#$qNRR{_hs2uB2P$#D5b`3vH)yZq!TQ}jKwz2?t)7f{(@xb0#g~+_(#kn)4{P3>P=6d)o(RVk)lR5k619e{ z1QmxpnwJ_;DLVBaYY=@AS4te4*QVEDn^i7>T9REE*go96Wd1rvg(|vQ@*TsqJN*5$ zhWXl^I!k^&p>$ z6kjJMoMw|QlWYp{Z8#WBcX$44R8HSoO!YaP`^?)-TUo&gRbjZ>Uy3SKtxy#n^oyU0evP{}psOw5Zq~V&p)5U-l<~)k~-J3VL=W-fYXfw=r zV4I+By0xuT1Ky^ubc&^-tLptpUOL3>!r6mnvQYX?)nfhoMW2SHga~HD9~I6}zi=>M zH1hG*S;h4SObRN_ezic88YYg)5sMOJ&e%5h$luN5PvF7a-4ar!Qf{8XR%(ba$X|H| z;+A#BxuM|`Md1nh9IQ}i$|5EasrxDxzelCW{uJf0^n|#%6+FFS4R(9Z=u&x3i&rp= zd6RVI@SHeU%w)8sTHYTl`JDKOSWLvfp+cJFvS22Ou9E+pJz}!nD*x$|$E=+#k{dgp z{RtHiDy_Q_n@%-`Q#XW(RbfKMdEV;ag_v@>HN1!BC~5Ro#n2>)ZneuH?>Ts(KjhJ-D_=7 zho(JVv^WXDI~PXy>Jg|{KB#FZz)2Ys0J!nr?C5)S@q5MO>Q0HgWE>qHgF$XC|I`&- zdAKaBtblev2CiiXCzYCFkD#r z8*MV$`GfT_xDJzv=UO9Tbv1H}HZvxd%H(+GBWhB1EGT0j#o4hUNB#boaa@g=)$PHGcs&)OPkcA7uECjb+)rl5HzNKbJVwd=sD z86tA4DtFA2W+xzomOnmZfjuAl(7&(#YFK|jp~82PmDSzcMg*loT`8ARtq+k|89_3ye7h1#*X2DC zKzsKRfRgDb#0gI%Ba+`>0HD}qkj0us3@o;fIlC1AT9fo zd_Vk9SNc`!-v=5$RJ4(ge>&dyj`Dp+{9Hu3Dk1+67GzIt+1B(MEa za{9O8&)LCMuJ=O@kvPbco_{jGe{22B=U17-51GOLIgj|R_dCwMqNX2WikyLeGx4w7 z|Es}&XaWFNr1L`(e}nY1FZeUg73KYq(cj?w@D2Z06inpS{P(zi_=-QHTzQKhBK8}U z|K&UW4EC#mynX}rvoHBG&aYEe`8%9ne9NDael_*hZ;*cYnm?obIxgCq|J&3*`=0;c zeBWz76YH;g%>aq>Uk*k3qVJ3NyXMdAf$aECl1TOk8dg`rKt?|R01@&9MAlq({u%%O E0F)DlPXGV_ literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/runlengthencoding.ods b/pandas/tests/io/data/runlengthencoding.ods new file mode 100644 index 0000000000000000000000000000000000000000..f28355a635c5b75d6ad4a8db21a36a7bbf61bb2c GIT binary patch literal 7898 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 literal 0 HcmV?d00001 diff --git a/pandas/tests/io/test_excel.py b/pandas/tests/io/test_excel.py index 112d14795d9bf..545254544fa2f 100644 --- a/pandas/tests/io/test_excel.py +++ b/pandas/tests/io/test_excel.py @@ -2556,3 +2556,135 @@ def test_excelwriter_fspath(self): with tm.ensure_clean('foo.xlsx') as path: writer = ExcelWriter(path) assert os.fspath(writer) == str(path) + + +@td.skip_if_no('odf') +class TestODFReader(SharedItems): + def test_get_sheet(self): + from pandas.io.excel._odfreader import _ODFReader + + pth = os.path.join(self.dirpath, 'datatypes.ods') + book = _ODFReader(pth) + + with pytest.raises(ValueError): + book.get_sheet(3.14) + + with pytest.raises(ValueError): + book.get_sheet_by_name("Invalid Sheet 77") + + with pytest.raises(IndexError): + book.get_sheet_by_index(-33) + + assert len(book.sheet_names) == 1 + assert book.sheet_names == ['Sheet1'] + + def test_read_types(self): + """Make sure we read ODF data types correctly + """ + sheet = self.get_exceldf( + 'datatypes', '.ods', header=None, engine='odf') + + expected = DataFrame( + [[1.0], + [1.25], + ['a'], + [pd.Timestamp(2003, 1, 2)], + [False], + [0.35], + [pd.Timedelta(hours=3, minutes=45), + pd.Timedelta(hours=17, minutes=53), + pd.Timedelta(hours=14, minutes=8)], + # though what should the value of a hyperlink be? + ['UBERON:0002101']]) + tm.assert_equal(sheet, expected) + + def test_read_invalid_types(self): + """Make sure we throw an exception when encountering a new value-type + + I had to manually create an invalid ods file by directly + editing the extracted xml. So it probably won't open in + LibreOffice correctly. + """ + with pytest.raises(ValueError, + match="Unrecognized type awesome_new_type"): + self.get_exceldf( + 'invalid_value_type', '.ods', header=None, engine='odf') + + def test_read_lower_diagonal(self): + """TextParser failed when given an irregular list of lists + + Make sure we can parse: + 1 + 2 3 + 4 5 6 + 7 8 9 10 + """ + sheet = self.get_exceldf( + 'lowerdiagonal', '.ods', 'Sheet1', + index_col=None, header=None, engine='odf') + + assert sheet.shape == (4, 4) + + def test_read_headers(self): + """Do we read headers correctly? + """ + sheet = self.get_exceldf( + 'headers', '.ods', 'Sheet1', index_col=0, engine='odf') + + expected = DataFrame.from_dict(OrderedDict([ + ("Header", ["Row 1", "Row 2"]), + ("Column 1", [1.0, 2.0]), + ("Column 2", [3.0, 4.0]), + # Empty Column + ("Column 4", [7.0, 8.0]), + # Empty Column 2 + ("Column 6", [11.0, 12.0])])) + expected.set_index("Header", inplace=True) + columns = ["Column 1", "Column 2", "Column 4", "Column 6"] + tm.assert_equal(sheet[columns], expected) + empties = [None, 'None.1'] + for name in empties: + for value in sheet[name]: + assert pd.isnull(value) + + def test_read_writer_table(self): + """ODF reuses the same table tags in Writer and Presentation files + + Test reading a table out of a text document + """ + table = self.get_exceldf( + 'writertable', '.odt', 'Table1', index_col=0, engine='odf') + + assert table.shape == (3, 3) + expected = DataFrame.from_dict(OrderedDict([ + ("Header", ["Row 1", "Row 2", "Row 3"]), + ("Column 1", [1.0, 2.0, 3.0]), + ("Unnamed: 2", [nan, nan, nan]), + ("Column 3", [7.0, 8.0, 9.0])])) + expected.set_index("Header", inplace=True) + columns = ["Column 1", "Column 3"] + tm.assert_equal(table[columns], expected[columns]) + + # make sure pandas gives a name to the unnamed column + for i in range(3): + assert pd.isnull(table["Unnamed: 2"][i]) + + def test_blank_row_repeat(self): + table = self.get_exceldf( + 'blank-row-repeat', '.ods', 'Value', engine='odf') + + assert table.shape == (14, 2) + assert table['value'][7] == 9.0 + assert pd.isnull(table['value'][8]) + assert not pd.isnull(table['value'][11]) + + def test_runlengthencoding(self): + """Calc will use repeat when adjacent columns have the same value. + """ + sheet = self.get_exceldf( + 'runlengthencoding', '.ods', 'Sheet1', header=None, engine='odf') + assert sheet.shape == (5, 3) + # check by column, not by row. + assert list(sheet[0]) == [1.0, 1.0, 2.0, 2.0, 2.0] + assert list(sheet[1]) == [1.0, 2.0, 2.0, 2.0, 2.0] + assert list(sheet[2]) == [1.0, 2.0, 2.0, 2.0, 2.0] From 8be4b671749f3f7229cd512d56027a3a07a9f495 Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Wed, 27 Feb 2019 22:31:31 -0800 Subject: [PATCH 02/55] Remove unneeded assignments --- pandas/io/excel/_odfreader.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index ce63a3240dba0..c846919611715 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -17,9 +17,6 @@ def __init__(self, filepath_or_stream): except ImportError: raise ImportError("Install odfpy for OpenDocument support") - self.filepath_or_stream = None - self.document = None - self.tables = None self.filepath_or_stream = filepath_or_stream self.document = document_load(filepath_or_stream) self.tables = self.document.getElementsByType(Table) From 77d9033ec68efa24b723d6db1f3f1f587481b999 Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Wed, 27 Feb 2019 22:32:03 -0800 Subject: [PATCH 03/55] Rename filepath_or_stream to filepath_or_buffer --- pandas/io/excel/_odfreader.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index c846919611715..0da90209f7305 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -7,18 +7,18 @@ class _ODFReader(object): Parameters ---------- - filepath_or_stream: string, path to be parsed or + filepath_or_buffer: string, path to be parsed or an open readable stream. """ - def __init__(self, filepath_or_stream): + def __init__(self, filepath_or_buffer): try: from odf.opendocument import load as document_load from odf.table import Table except ImportError: raise ImportError("Install odfpy for OpenDocument support") - self.filepath_or_stream = filepath_or_stream - self.document = document_load(filepath_or_stream) + self.filepath_or_buffer = filepath_or_buffer + self.document = document_load(filepath_or_buffer) self.tables = self.document.getElementsByType(Table) @property From 47b2ffb2dda8b1efc9a0babd688047e87a869029 Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Wed, 27 Feb 2019 22:32:56 -0800 Subject: [PATCH 04/55] Use compat.string_types instead of str --- pandas/io/excel/_odfreader.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 0da90209f7305..bfec63c922038 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -1,4 +1,5 @@ import pandas +from pandas import compat from pandas.io.parsers import TextParser @@ -37,7 +38,7 @@ def get_sheet_by_name(self, name): def get_sheet(self, name): """Given a sheet name or index, return the root ODF Table node """ - if isinstance(name, str): + if isinstance(name, compat.string_types): return self.get_sheet_by_name(name) elif isinstance(name, int): return self.get_sheet_by_index(name) From 0fa2ac9315cc0020b30f1e60170978ada5150958 Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Wed, 27 Feb 2019 22:34:03 -0800 Subject: [PATCH 05/55] Use pd as name as pandas --- pandas/io/excel/_odfreader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index bfec63c922038..d6d8ed3894603 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -1,4 +1,4 @@ -import pandas +import pandas as pd from pandas import compat from pandas.io.parsers import TextParser @@ -141,7 +141,7 @@ def __get_cell_value(self, cell): return float(cell_value) elif cell_type == 'date': cell_value = cell.attributes.get((OFFICENS, 'date-value')) - return pandas.Timestamp(cell_value) + return pd.Timestamp(cell_value) elif cell_type == 'time': cell_value = cell.attributes.get((OFFICENS, 'time-value')) return(pandas_isoduration_compatibility(cell_value)) @@ -160,4 +160,4 @@ def pandas_isoduration_compatibility(duration): """ if duration.startswith('PT'): duration = 'P0DT' + duration[2:] - return pandas.Timedelta(duration) + return pd.Timedelta(duration) From e6e2365bfb8ad86df29f54633498854dc4b69665 Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Wed, 27 Feb 2019 22:35:11 -0800 Subject: [PATCH 06/55] Use single underscore for private functions --- pandas/io/excel/_odfreader.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index d6d8ed3894603..e2af817cea7bc 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -66,8 +66,8 @@ def __get_table(self, sheet): empty_cells = 0 table_row = [] for j, sheet_cell in enumerate(sheet_cells): - value = self.__get_cell_value(sheet_cell) - column_repeat = self.__get_cell_repeat(sheet_cell) + value = self._get_cell_value(sheet_cell) + column_repeat = self._get_cell_repeat(sheet_cell) if len(sheet_cell.childNodes) == 0: empty_cells += column_repeat @@ -80,8 +80,8 @@ def __get_table(self, sheet): if max_row_len < len(table_row): max_row_len = len(table_row) - row_repeat = self.__get_row_repeat(sheet_row) - if self.__is_empty_row(sheet_row): + row_repeat = self._get_row_repeat(sheet_row) + if self._is_empty_row(sheet_row): empty_rows += row_repeat else: if empty_rows > 0: @@ -97,7 +97,7 @@ def __get_table(self, sheet): return table - def __get_row_repeat(self, row): + def _get_row_repeat(self, row): """Return number of times this row was repeated Repeating an empty row appeared to be a common way @@ -109,14 +109,14 @@ def __get_row_repeat(self, row): return 1 return int(repeat) - def __get_cell_repeat(self, cell): + def _get_cell_repeat(self, cell): from odf.namespaces import TABLENS repeat = cell.attributes.get((TABLENS, 'number-columns-repeated')) if repeat is None: return 1 return int(repeat) - def __is_empty_row(self, row): + def _is_empty_row(self, row): """Helper function to find empty rows """ for column in row.childNodes: @@ -125,7 +125,7 @@ def __is_empty_row(self, row): return True - def __get_cell_value(self, cell): + def _get_cell_value(self, cell): from odf.namespaces import OFFICENS cell_type = cell.attributes.get((OFFICENS, 'value-type')) if cell_type == 'boolean': From 1bbf28400fef308df898d748af8fc70ed6d9cfed Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Wed, 27 Feb 2019 22:35:49 -0800 Subject: [PATCH 07/55] Return an unparsed sheet. xlrd's versions of get_sheet_by_index return unparsed sheet objects which the parse function then feeds to get_sheet_data to actually turn into a DataFrame Use get_sheet_data as name of function that converts a sheet into a structure to be passed to TextParser. (To be more consistent with xlrd) --- pandas/io/excel/_odfreader.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index e2af817cea7bc..b60c8dd63ee17 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -29,13 +29,13 @@ def sheet_names(self): return [t.attributes[(TABLENS, 'name')] for t in self.tables] def get_sheet_by_index(self, index): - return self.__get_table(self.tables[index]) + return self.tables[index] def get_sheet_by_name(self, name): i = self.sheet_names.index(name) - return self.__get_table(self.tables[i]) + return self.tables[i] - def get_sheet(self, name): + def _get_sheet(self, name): """Given a sheet name or index, return the root ODF Table node """ if isinstance(name, compat.string_types): @@ -48,11 +48,12 @@ def get_sheet(self, name): 'a string or integer'.format(type(name))) def parse(self, sheet_name=0, **kwds): - data = self.get_sheet(sheet_name) + tree = self._get_sheet(sheet_name) + data = self.get_sheet_data(tree, convert_float=False) parser = TextParser(data, **kwds) return parser.read() - def __get_table(self, sheet): + def get_sheet_data(self, sheet, convert_float): """Parse an ODF Table into a list of lists """ from odf.table import TableCell, TableRow From d5c7ec014df12a2264306db6718905bf0693bb73 Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Wed, 27 Feb 2019 22:44:11 -0800 Subject: [PATCH 08/55] Move ODFReader get_sheet exception testing code to its own function that ends with _raises --- pandas/tests/io/test_excel.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pandas/tests/io/test_excel.py b/pandas/tests/io/test_excel.py index 545254544fa2f..b6d6e0f093ff2 100644 --- a/pandas/tests/io/test_excel.py +++ b/pandas/tests/io/test_excel.py @@ -2566,8 +2566,17 @@ def test_get_sheet(self): pth = os.path.join(self.dirpath, 'datatypes.ods') book = _ODFReader(pth) + assert len(book.sheet_names) == 1 + assert book.sheet_names == ['Sheet1'] + + def test_get_sheet_raises(self): + from pandas.io.excel._odfreader import _ODFReader + + pth = os.path.join(self.dirpath, 'datatypes.ods') + book = _ODFReader(pth) + with pytest.raises(ValueError): - book.get_sheet(3.14) + book._get_sheet(3.14) with pytest.raises(ValueError): book.get_sheet_by_name("Invalid Sheet 77") @@ -2575,9 +2584,6 @@ def test_get_sheet(self): with pytest.raises(IndexError): book.get_sheet_by_index(-33) - assert len(book.sheet_names) == 1 - assert book.sheet_names == ['Sheet1'] - def test_read_types(self): """Make sure we read ODF data types correctly """ From 691f1e941ab21b9106131e856cd8118b7155104f Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Wed, 27 Feb 2019 22:45:33 -0800 Subject: [PATCH 09/55] Append _raises to end of function name that tests exceptions --- pandas/tests/io/test_excel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/io/test_excel.py b/pandas/tests/io/test_excel.py index b6d6e0f093ff2..0dc8d8193c52d 100644 --- a/pandas/tests/io/test_excel.py +++ b/pandas/tests/io/test_excel.py @@ -2604,7 +2604,7 @@ def test_read_types(self): ['UBERON:0002101']]) tm.assert_equal(sheet, expected) - def test_read_invalid_types(self): + def test_read_invalid_types_raises(self): """Make sure we throw an exception when encountering a new value-type I had to manually create an invalid ods file by directly From 93c2b660c07a46a8da6770a19b216f9c8757ad58 Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Wed, 27 Feb 2019 22:46:12 -0800 Subject: [PATCH 10/55] Remove test docstrings that include no useful information --- pandas/tests/io/test_excel.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pandas/tests/io/test_excel.py b/pandas/tests/io/test_excel.py index 0dc8d8193c52d..ac94780d80c4b 100644 --- a/pandas/tests/io/test_excel.py +++ b/pandas/tests/io/test_excel.py @@ -2585,8 +2585,6 @@ def test_get_sheet_raises(self): book.get_sheet_by_index(-33) def test_read_types(self): - """Make sure we read ODF data types correctly - """ sheet = self.get_exceldf( 'datatypes', '.ods', header=None, engine='odf') @@ -2632,8 +2630,6 @@ def test_read_lower_diagonal(self): assert sheet.shape == (4, 4) def test_read_headers(self): - """Do we read headers correctly? - """ sheet = self.get_exceldf( 'headers', '.ods', 'Sheet1', index_col=0, engine='odf') From 394c4bd2793e96c3d54c38ff66fb4c899863359b Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Wed, 27 Feb 2019 22:58:41 -0800 Subject: [PATCH 11/55] Indicate likely minimum version. I started this a couple of years ago and so was using a 1.3ish release. Looking through the repository, it looks like the APIs I'm using are stable since then. --- pandas/io/excel/_odfreader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index b60c8dd63ee17..d81d56c975435 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -16,7 +16,7 @@ def __init__(self, filepath_or_buffer): from odf.opendocument import load as document_load from odf.table import Table except ImportError: - raise ImportError("Install odfpy for OpenDocument support") + raise ImportError("Install odfpy >= 1.3 for OpenDocument support") self.filepath_or_buffer = filepath_or_buffer self.document = document_load(filepath_or_buffer) From b149d844e7de2933f53028cb4e2796484fb0883d Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Fri, 5 Apr 2019 10:49:43 -0700 Subject: [PATCH 12/55] Convert notes about some OpenDocument tests to comments --- pandas/tests/io/test_excel.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/pandas/tests/io/test_excel.py b/pandas/tests/io/test_excel.py index ac94780d80c4b..c55f275520444 100644 --- a/pandas/tests/io/test_excel.py +++ b/pandas/tests/io/test_excel.py @@ -2603,26 +2603,20 @@ def test_read_types(self): tm.assert_equal(sheet, expected) def test_read_invalid_types_raises(self): - """Make sure we throw an exception when encountering a new value-type - - I had to manually create an invalid ods file by directly - editing the extracted xml. So it probably won't open in - LibreOffice correctly. - """ + # the invalid_value_type.ods required manually editing + # of the included content.xml file with pytest.raises(ValueError, match="Unrecognized type awesome_new_type"): self.get_exceldf( 'invalid_value_type', '.ods', header=None, engine='odf') def test_read_lower_diagonal(self): - """TextParser failed when given an irregular list of lists + # Make sure we can parse: + # 1 + # 2 3 + # 4 5 6 + # 7 8 9 10 - Make sure we can parse: - 1 - 2 3 - 4 5 6 - 7 8 9 10 - """ sheet = self.get_exceldf( 'lowerdiagonal', '.ods', 'Sheet1', index_col=None, header=None, engine='odf') @@ -2650,10 +2644,9 @@ def test_read_headers(self): assert pd.isnull(value) def test_read_writer_table(self): - """ODF reuses the same table tags in Writer and Presentation files + # Also test reading tables from an text OpenDocument file + # (.odt) - Test reading a table out of a text document - """ table = self.get_exceldf( 'writertable', '.odt', 'Table1', index_col=0, engine='odf') @@ -2681,8 +2674,6 @@ def test_blank_row_repeat(self): assert not pd.isnull(table['value'][11]) def test_runlengthencoding(self): - """Calc will use repeat when adjacent columns have the same value. - """ sheet = self.get_exceldf( 'runlengthencoding', '.ods', 'Sheet1', header=None, engine='odf') assert sheet.shape == (5, 3) From 19587b3628478224b6a534eca01704477d9e061f Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Fri, 5 Apr 2019 11:22:01 -0700 Subject: [PATCH 13/55] Add note about new OpenDocument functionality to whatsnew --- doc/source/whatsnew/v0.25.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 9db8bef8debcd..7d09e44128deb 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -41,6 +41,7 @@ Other Enhancements - :meth:`DataFrame.query` and :meth:`DataFrame.eval` now supports quoting column names with backticks to refer to names with spaces (:issue:`6508`) - :func:`merge_asof` now gives a more clear error message when merge keys are categoricals that are not equal (:issue:`26136`) - :meth:`pandas.core.window.Rolling` supports exponential (or Poisson) window type (:issue:`21303`) +- :func:`pandas.io.excel.read_excel` supports reading OpenDocument tables. Specify engine='odf' to enable. (:issue:`9070`) .. _whatsnew_0250.api_breaking: From 60a5bc1e3c08dffc87c3b642308debe38ade6cf7 Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Sat, 6 Apr 2019 21:48:55 -0700 Subject: [PATCH 14/55] Sort imports correctly --- pandas/io/excel/_odfreader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index d81d56c975435..b66ada4803333 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -1,5 +1,6 @@ import pandas as pd from pandas import compat + from pandas.io.parsers import TextParser From 1fef008870371dcb259bda332e842bf94da0f234 Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Sun, 7 Apr 2019 20:41:56 -0700 Subject: [PATCH 15/55] Use str instead of compat.string_types Pandas dropped support for Python 2. --- pandas/io/excel/_odfreader.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index b66ada4803333..c5f6d5af9049d 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -1,5 +1,4 @@ import pandas as pd -from pandas import compat from pandas.io.parsers import TextParser @@ -39,7 +38,7 @@ def get_sheet_by_name(self, name): def _get_sheet(self, name): """Given a sheet name or index, return the root ODF Table node """ - if isinstance(name, compat.string_types): + if isinstance(name, str): return self.get_sheet_by_name(name) elif isinstance(name, int): return self.get_sheet_by_index(name) From 7148995984fcfddcde153245f351e294dee6e5de Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Tue, 14 May 2019 14:19:41 -0700 Subject: [PATCH 16/55] Remove leading underscore from ODFParser --- pandas/io/excel/_base.py | 4 ++-- pandas/io/excel/_odfreader.py | 2 +- pandas/tests/io/test_excel.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index 828343be81203..8c245743f146f 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -780,11 +780,11 @@ class ExcelFile: """ from pandas.io.excel._xlrd import _XlrdReader - from pandas.io.excel._odfreader import _ODFReader + from pandas.io.excel._odfreader import ODFReader _engines = { 'xlrd': _XlrdReader, - 'odf': _ODFReader, + 'odf': ODFReader, } def __init__(self, io, engine=None): diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index c5f6d5af9049d..ae4901ed0104f 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -3,7 +3,7 @@ from pandas.io.parsers import TextParser -class _ODFReader(object): +class ODFReader(object): """Read tables out of OpenDocument formatted files Parameters diff --git a/pandas/tests/io/test_excel.py b/pandas/tests/io/test_excel.py index c55f275520444..6b4c9466ca7a6 100644 --- a/pandas/tests/io/test_excel.py +++ b/pandas/tests/io/test_excel.py @@ -2561,19 +2561,19 @@ def test_excelwriter_fspath(self): @td.skip_if_no('odf') class TestODFReader(SharedItems): def test_get_sheet(self): - from pandas.io.excel._odfreader import _ODFReader + from pandas.io.excel._odfreader import ODFReader pth = os.path.join(self.dirpath, 'datatypes.ods') - book = _ODFReader(pth) + book = ODFReader(pth) assert len(book.sheet_names) == 1 assert book.sheet_names == ['Sheet1'] def test_get_sheet_raises(self): - from pandas.io.excel._odfreader import _ODFReader + from pandas.io.excel._odfreader import ODFReader pth = os.path.join(self.dirpath, 'datatypes.ods') - book = _ODFReader(pth) + book = ODFReader(pth) with pytest.raises(ValueError): book._get_sheet(3.14) From 5db1a0ba68ba36cc7f732ecb79c9c6f9f0f05a43 Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Tue, 14 May 2019 20:07:09 -0700 Subject: [PATCH 17/55] Remove obsolete class (object) --- pandas/io/excel/_odfreader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index ae4901ed0104f..a487212c15a25 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -3,7 +3,7 @@ from pandas.io.parsers import TextParser -class ODFReader(object): +class ODFReader: """Read tables out of OpenDocument formatted files Parameters From 83c024365dc24889f8c624868c26ec651412039d Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Fri, 14 Jun 2019 16:16:32 -0700 Subject: [PATCH 18/55] Improve docstring text --- pandas/io/excel/_odfreader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index a487212c15a25..c28398da0beb3 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -24,7 +24,7 @@ def __init__(self, filepath_or_buffer): @property def sheet_names(self): - """Return table names is the document""" + """Return a list of sheet names present in the document""" from odf.namespaces import TABLENS return [t.attributes[(TABLENS, 'name')] for t in self.tables] From 8302fd71fb7cdb31299870e2663067fcfe3d7323 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Fri, 28 Jun 2019 11:42:18 -0500 Subject: [PATCH 19/55] Added test_odf --- pandas/tests/io/excel/test_odf.py | 79 ++++++++++++++++++------------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/pandas/tests/io/excel/test_odf.py b/pandas/tests/io/excel/test_odf.py index f5c9e598ca3cf..be4345380348e 100644 --- a/pandas/tests/io/excel/test_odf.py +++ b/pandas/tests/io/excel/test_odf.py @@ -1,19 +1,28 @@ -odf = pytest.importorskip("odf") +from collections import OrderedDict +import numpy as np +import pytest -def test_get_sheet(self): +import pandas as pd +import pandas.util.testing as tm + +pytest.importorskip("odf") + + +def test_get_sheet(datapath): from pandas.io.excel._odfreader import ODFReader - pth = os.path.join(self.dirpath, 'datatypes.ods') + pth = datapath("io", "data", "datatypes.ods") book = ODFReader(pth) assert len(book.sheet_names) == 1 assert book.sheet_names == ['Sheet1'] -def test_get_sheet_raises(self): + +def test_get_sheet_raises(datapath): from pandas.io.excel._odfreader import ODFReader - pth = os.path.join(self.dirpath, 'datatypes.ods') + pth = datapath("io", "data", 'datatypes.ods') book = ODFReader(pth) with pytest.raises(ValueError): @@ -25,11 +34,12 @@ def test_get_sheet_raises(self): with pytest.raises(IndexError): book.get_sheet_by_index(-33) -def test_read_types(self): - sheet = self.get_exceldf( - 'datatypes', '.ods', header=None, engine='odf') - expected = DataFrame( +def test_read_types(datapath): + path = datapath("io", "data", "datatypes.ods") + sheet = pd.read_excel(path, header=None, engine='odf') + + expected = pd.DataFrame( [[1.0], [1.25], ['a'], @@ -43,32 +53,34 @@ def test_read_types(self): ['UBERON:0002101']]) tm.assert_equal(sheet, expected) -def test_read_invalid_types_raises(self): + +def test_read_invalid_types_raises(datapath): # the invalid_value_type.ods required manually editing # of the included content.xml file + path = datapath("io", "data", "invalid_value_type.ods") with pytest.raises(ValueError, match="Unrecognized type awesome_new_type"): - self.get_exceldf( - 'invalid_value_type', '.ods', header=None, engine='odf') + pd.read_excel(path, header=None, engine='odf') -def test_read_lower_diagonal(self): + +def test_read_lower_diagonal(datapath): # Make sure we can parse: # 1 # 2 3 # 4 5 6 # 7 8 9 10 - - sheet = self.get_exceldf( - 'lowerdiagonal', '.ods', 'Sheet1', - index_col=None, header=None, engine='odf') + path = datapath("io", "data", "lowerdiagonal.ods") + sheet = pd.read_excel(path, 'Sheet1', + index_col=None, header=None, engine='odf') assert sheet.shape == (4, 4) -def test_read_headers(self): - sheet = self.get_exceldf( - 'headers', '.ods', 'Sheet1', index_col=0, engine='odf') - expected = DataFrame.from_dict(OrderedDict([ +def test_read_headers(datapath): + path = datapath("io", "data", "headers.ods") + sheet = pd.read_excel(path, 'Sheet1', index_col=0, engine='odf') + + expected = pd.DataFrame.from_dict(OrderedDict([ ("Header", ["Row 1", "Row 2"]), ("Column 1", [1.0, 2.0]), ("Column 2", [3.0, 4.0]), @@ -84,18 +96,19 @@ def test_read_headers(self): for value in sheet[name]: assert pd.isnull(value) -def test_read_writer_table(self): + +def test_read_writer_table(datapath): # Also test reading tables from an text OpenDocument file # (.odt) - table = self.get_exceldf( - 'writertable', '.odt', 'Table1', index_col=0, engine='odf') + path = datapath("io", "data", "writertable.odt") + table = pd.read_excel(path, 'Table1', index_col=0, engine='odf') assert table.shape == (3, 3) - expected = DataFrame.from_dict(OrderedDict([ + expected = pd.DataFrame.from_dict(OrderedDict([ ("Header", ["Row 1", "Row 2", "Row 3"]), ("Column 1", [1.0, 2.0, 3.0]), - ("Unnamed: 2", [nan, nan, nan]), + ("Unnamed: 2", [np.nan, np.nan, np.nan]), ("Column 3", [7.0, 8.0, 9.0])])) expected.set_index("Header", inplace=True) columns = ["Column 1", "Column 3"] @@ -105,18 +118,20 @@ def test_read_writer_table(self): for i in range(3): assert pd.isnull(table["Unnamed: 2"][i]) -def test_blank_row_repeat(self): - table = self.get_exceldf( - 'blank-row-repeat', '.ods', 'Value', engine='odf') + +def test_blank_row_repeat(datapath): + path = datapath("io", "data", "blank-row-repeat.ods") + table = pd.read_excel(path, 'Value', engine='odf') assert table.shape == (14, 2) assert table['value'][7] == 9.0 assert pd.isnull(table['value'][8]) assert not pd.isnull(table['value'][11]) -def test_runlengthencoding(self): - sheet = self.get_exceldf( - 'runlengthencoding', '.ods', 'Sheet1', header=None, engine='odf') + +def test_runlengthencoding(datapath): + path = datapath("io", "data", "runlengthencoding.ods") + sheet = pd.read_excel(path, 'Sheet1', header=None, engine='odf') assert sheet.shape == (5, 3) # check by column, not by row. assert list(sheet[0]) == [1.0, 1.0, 2.0, 2.0, 2.0] From 47597c96c4fc2b82db6efdd57a6556ff5a18db48 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sat, 29 Jun 2019 10:38:53 -0500 Subject: [PATCH 20/55] Class naming consistency --- pandas/io/excel/_base.py | 6 +++--- pandas/io/excel/_odfreader.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index a382df898bebb..244b0106f9c4a 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -768,14 +768,14 @@ class ExcelFile: Acceptable values are None or ``xlrd``. """ - from pandas.io.excel._xlrd import _XlrdReader + from pandas.io.excel._odfreader import ODFReader from pandas.io.excel._openpyxl import _OpenpyxlReader - from pandas.io.excel._odfreader import ODFReader + from pandas.io.excel._xlrd import _XlrdReader _engines = { 'xlrd': _XlrdReader, 'openpyxl': _OpenpyxlReader, - 'odf': ODFReader, + 'odf': _ODFReader, } def __init__(self, io, engine=None): diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index c28398da0beb3..117b24792df7e 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -3,7 +3,7 @@ from pandas.io.parsers import TextParser -class ODFReader: +class _ODFReader: """Read tables out of OpenDocument formatted files Parameters From 9e1799a933aaaea6840c5f35f17a702dc8b74cbf Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sat, 29 Jun 2019 10:39:41 -0500 Subject: [PATCH 21/55] Whatsnew linting --- doc/source/whatsnew/v0.25.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 76ca44973e685..4fc58eccbf1b5 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -160,7 +160,7 @@ Other enhancements - Added new option ``plotting.backend`` to be able to select a plotting backend different than the existing ``matplotlib`` one. Use ``pandas.set_option('plotting.backend', '')`` where `` Date: Sat, 29 Jun 2019 10:44:41 -0500 Subject: [PATCH 22/55] Added optional dependency load --- pandas/compat/_optional.py | 1 + pandas/io/excel/_odfreader.py | 11 ++++------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pandas/compat/_optional.py b/pandas/compat/_optional.py index 31746dc3d6c16..09726254b9503 100644 --- a/pandas/compat/_optional.py +++ b/pandas/compat/_optional.py @@ -13,6 +13,7 @@ "lxml.etree": "3.8.0", "matplotlib": "2.2.2", "numexpr": "2.6.2", + "odfpy", "1.3.0", "openpyxl": "2.4.8", "pandas_gbq": "0.8.0", "pyarrow": "0.9.0", diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 117b24792df7e..50f8bf4ee5fdf 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -1,5 +1,7 @@ import pandas as pd +from pandas.compat._optional import import_optional_dependency + from pandas.io.parsers import TextParser @@ -12,15 +14,10 @@ class _ODFReader: an open readable stream. """ def __init__(self, filepath_or_buffer): - try: - from odf.opendocument import load as document_load - from odf.table import Table - except ImportError: - raise ImportError("Install odfpy >= 1.3 for OpenDocument support") - - self.filepath_or_buffer = filepath_or_buffer + import_optional_dependency("odf") self.document = document_load(filepath_or_buffer) self.tables = self.document.getElementsByType(Table) + super().__init__(filepath_or_buffer) @property def sheet_names(self): From 39cfecf17c571225f619f7334de0eecbea7c997a Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sat, 29 Jun 2019 10:49:16 -0500 Subject: [PATCH 23/55] typo --- pandas/compat/_optional.py | 2 +- pandas/io/excel/_base.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/compat/_optional.py b/pandas/compat/_optional.py index 09726254b9503..620884d66821c 100644 --- a/pandas/compat/_optional.py +++ b/pandas/compat/_optional.py @@ -13,7 +13,7 @@ "lxml.etree": "3.8.0", "matplotlib": "2.2.2", "numexpr": "2.6.2", - "odfpy", "1.3.0", + "odfpy": "1.3.0", "openpyxl": "2.4.8", "pandas_gbq": "0.8.0", "pyarrow": "0.9.0", diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index 244b0106f9c4a..d10a40541bb6c 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -768,7 +768,7 @@ class ExcelFile: Acceptable values are None or ``xlrd``. """ - from pandas.io.excel._odfreader import ODFReader + from pandas.io.excel._odfreader import _ODFReader from pandas.io.excel._openpyxl import _OpenpyxlReader from pandas.io.excel._xlrd import _XlrdReader From 8a9a66c39a0adcc75540a2f6c492929cd2ec303c Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sat, 29 Jun 2019 11:39:13 -0500 Subject: [PATCH 24/55] Updated inheritance to use excel reader interface --- pandas/io/excel/_odfreader.py | 64 +++++++++++++++++-------------- pandas/tests/io/excel/test_odf.py | 26 ------------- 2 files changed, 35 insertions(+), 55 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 50f8bf4ee5fdf..5ff7a5ce30ca7 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -1,11 +1,15 @@ +from typing import List + import pandas as pd +from pandas._typing import FilePathOrBuffer, Scalar + from pandas.compat._optional import import_optional_dependency -from pandas.io.parsers import TextParser +from pandas.io.excel._base import _BaseExcelReader -class _ODFReader: +class _ODFReader(_BaseExcelReader): """Read tables out of OpenDocument formatted files Parameters @@ -13,42 +17,45 @@ class _ODFReader: filepath_or_buffer: string, path to be parsed or an open readable stream. """ - def __init__(self, filepath_or_buffer): + def __init__(self, filepath_or_buffer: FilePathOrBuffer): import_optional_dependency("odf") - self.document = document_load(filepath_or_buffer) - self.tables = self.document.getElementsByType(Table) super().__init__(filepath_or_buffer) @property - def sheet_names(self): + def _workbook_class(self): + from odf.opendocument import OpenDocument + return OpenDocument + + def load_workbook(self, filepath_or_buffer: FilePathOrBuffer): + from odf.opendocument import load + return load(filepath_or_buffer) + + @property + def sheet_names(self) -> List[str]: """Return a list of sheet names present in the document""" from odf.namespaces import TABLENS - return [t.attributes[(TABLENS, 'name')] for t in self.tables] + from odf.table import Table - def get_sheet_by_index(self, index): - return self.tables[index] + tables = self.book.getElementsByType(Table) + return [t.attributes[(TABLENS, 'name')] for t in tables] - def get_sheet_by_name(self, name): - i = self.sheet_names.index(name) - return self.tables[i] + def get_sheet_by_index(self, index: int): + from odf.table import Table + tables = self.book.getElementsByType(Table) + return tables[index] - def _get_sheet(self, name): - """Given a sheet name or index, return the root ODF Table node - """ - if isinstance(name, str): - return self.get_sheet_by_name(name) - elif isinstance(name, int): - return self.get_sheet_by_index(name) - else: - raise ValueError( - 'Unrecognized sheet identifier type {}. Please use' - 'a string or integer'.format(type(name))) + def get_sheet_by_name(self, name: str): + from odf.namespaces import TABLENS + from odf.table import Table + + tables = self.book.getElementsByType(Table) - def parse(self, sheet_name=0, **kwds): - tree = self._get_sheet(sheet_name) - data = self.get_sheet_data(tree, convert_float=False) - parser = TextParser(data, **kwds) - return parser.read() + key = (TABLENS, "name") + for table in tables: + if table.attributes[key] == name: + return table + + raise ValueError("sheet {name} not found".format(name)) def get_sheet_data(self, sheet, convert_float): """Parse an ODF Table into a list of lists @@ -97,7 +104,6 @@ def get_sheet_data(self, sheet, convert_float): def _get_row_repeat(self, row): """Return number of times this row was repeated - Repeating an empty row appeared to be a common way of representing sparse rows in the table. """ diff --git a/pandas/tests/io/excel/test_odf.py b/pandas/tests/io/excel/test_odf.py index be4345380348e..43ad5cdfa7893 100644 --- a/pandas/tests/io/excel/test_odf.py +++ b/pandas/tests/io/excel/test_odf.py @@ -9,32 +9,6 @@ pytest.importorskip("odf") -def test_get_sheet(datapath): - from pandas.io.excel._odfreader import ODFReader - - pth = datapath("io", "data", "datatypes.ods") - book = ODFReader(pth) - - assert len(book.sheet_names) == 1 - assert book.sheet_names == ['Sheet1'] - - -def test_get_sheet_raises(datapath): - from pandas.io.excel._odfreader import ODFReader - - pth = datapath("io", "data", 'datatypes.ods') - book = ODFReader(pth) - - with pytest.raises(ValueError): - book._get_sheet(3.14) - - with pytest.raises(ValueError): - book.get_sheet_by_name("Invalid Sheet 77") - - with pytest.raises(IndexError): - book.get_sheet_by_index(-33) - - def test_read_types(datapath): path = datapath("io", "data", "datatypes.ods") sheet = pd.read_excel(path, header=None, engine='odf') From fd7663f04b5721c21978eff81cbbc39ed58419d4 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sat, 29 Jun 2019 11:48:47 -0500 Subject: [PATCH 25/55] Added ods test files --- pandas/tests/io/data/blank.ods | Bin 0 -> 2813 bytes pandas/tests/io/data/blank_with_header.ods | Bin 0 -> 2893 bytes pandas/tests/io/data/test1.ods | Bin 0 -> 4440 bytes pandas/tests/io/data/test2.ods | Bin 0 -> 2877 bytes pandas/tests/io/data/test3.ods | Bin 0 -> 2891 bytes pandas/tests/io/data/test4.ods | Bin 0 -> 2992 bytes pandas/tests/io/data/test5.ods | Bin 0 -> 2906 bytes pandas/tests/io/data/test_converters.ods | Bin 0 -> 3287 bytes pandas/tests/io/data/test_index_name_pre17.ods | Bin 0 -> 3699 bytes pandas/tests/io/data/test_multisheet.ods | Bin 0 -> 3797 bytes pandas/tests/io/data/test_squeeze.ods | Bin 0 -> 3218 bytes pandas/tests/io/data/test_types.ods | Bin 0 -> 3489 bytes pandas/tests/io/data/testdateoverflow.ods | Bin 0 -> 3422 bytes pandas/tests/io/data/testdtype.ods | Bin 0 -> 3196 bytes pandas/tests/io/data/testmultiindex.ods | Bin 0 -> 5575 bytes pandas/tests/io/data/testskiprows.ods | Bin 0 -> 3235 bytes pandas/tests/io/data/times_1900.ods | Bin 0 -> 3181 bytes pandas/tests/io/data/times_1904.ods | Bin 0 -> 3215 bytes 18 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 pandas/tests/io/data/blank.ods create mode 100644 pandas/tests/io/data/blank_with_header.ods create mode 100644 pandas/tests/io/data/test1.ods create mode 100644 pandas/tests/io/data/test2.ods create mode 100644 pandas/tests/io/data/test3.ods create mode 100644 pandas/tests/io/data/test4.ods create mode 100644 pandas/tests/io/data/test5.ods create mode 100644 pandas/tests/io/data/test_converters.ods create mode 100644 pandas/tests/io/data/test_index_name_pre17.ods create mode 100644 pandas/tests/io/data/test_multisheet.ods create mode 100644 pandas/tests/io/data/test_squeeze.ods create mode 100644 pandas/tests/io/data/test_types.ods create mode 100644 pandas/tests/io/data/testdateoverflow.ods create mode 100644 pandas/tests/io/data/testdtype.ods create mode 100644 pandas/tests/io/data/testmultiindex.ods create mode 100644 pandas/tests/io/data/testskiprows.ods create mode 100644 pandas/tests/io/data/times_1900.ods create mode 100644 pandas/tests/io/data/times_1904.ods diff --git a/pandas/tests/io/data/blank.ods b/pandas/tests/io/data/blank.ods new file mode 100644 index 0000000000000000000000000000000000000000..7ded3c3c1d688242b6af3b2bdd3afe617a23c33c GIT binary patch literal 2813 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 literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/blank_with_header.ods b/pandas/tests/io/data/blank_with_header.ods new file mode 100644 index 0000000000000000000000000000000000000000..0a2e696267fda9e6f01daeed31c3ccdcac163555 GIT binary patch literal 2893 zcmZ`*2{_bSA09NwzGdH+5N2%GzFcD;4Wn#>Fc@3LHj@k@dl|}3sLZt{yFu35SRygE z5JDsq5y~}%RPN|L-_!T?-FyDedCvL&&v}36dH&D)p7({@Fq~ik{N1DgDZ#qwAll1u zXQp*@AR2|mg`tpPVZnhuNNiwe2skpt7Zi%b1Y$s;VW<$_P@hX^R0tM?35!4>eK7$j z6c$da$p>KkUO@`*>gx4%6axUDPCMrKBn%c8jKYAT(7}UFk3;5Fxq1cO8Z)y+=gFZ= zL`e}GD$$$wz5#p zCOPQ!UnVObiUBngQFi8IivFZZ{W%c>>Kqk5qP+C96;XorS@ZAKnm*nA3Kv=bArRe? z2`pG!&vY+|oi;%GLVag^2Ci=Ej@{DOIY%g0yK)OvJsF7UnzwrHH#9kUeziw|t@gB! zyRpLdZ$UZswp;K^PQmHQkJ1VH@V54TVUaI4B)+oLx>mC=e=Re7Q8$#u{xkxgBbj|` zEcBVtnFrZri;TR*(|KXNO9!=<8Y2>UTYT<>#-i3F?A~Sb( z(o4F2lRHl*;`f2BqMu~a&Ip)T?@y~`t0Mz!99>%rgxI_Bo6qoJH-+>ghCv+r79s{= zKH3ZIvtCQ-^NV?Ka``aFWKv~ht`Y^hi#U&+Q*sGK2`(@!GG&NZsM;m8^KT0n2);=0 zItdj|IjiDcAmDIrWarJ<++973;YjAKdb(+@(g6T<-Y5 znBHGaEA}qN8=TvMEOoZBAqVAU6pU<*Uujk0cqS8bwutT}Kkl2H*~M}SD^-xIZm?I; zuz5~YDKGTT`Qc#|UXLS^P+EA$m70#I*kd%`bFAt3@D*KToGdc%qQ-;)eMn|$v)pF{R($0hQ{Q14EbSJf=7FFscY2R;EN5{8JgKuPmn+uN$2;J|-Rp!c#Q6F7 z&@TK*Z=@l+76%45bUvfX=>bb{^RDH9#@}p z^by18%M$j(@^0>@rgx(^E4Mn;Kb@%4Ye(se?7!D<_t8R>K`Wu}#YX9_8QwWqEeU

F#(eioN{k% z0KPdnIl8h&|5edBrYk?-CBpIIM)LhrXu3i~J$p@TjkwT^TBNI0-FCcK+Tp#;SFGpVI(FC^3_Ek<~{5}!Ih;Y z>SgV@?a_^cgU*cZA-DqBHnOT!-8I}PF&XaY?4<2@3+*@)LL!@w+^hEg@<1T(xq)NZ zdOj7aF_RXORe)+1lvSTNXqojrsa@kf0cT8R$PHq6t&ZSGsa2}W}#>byI!ei zb%GU&X;6aQX&8rZdy-wAoXTdZO63MYTS=5Y=lc;+{ov&)yS*#P2g6A!@!(mu=)z|9 zM-Q{R6}O$$(;&{J3vq+6XguFj29#wE&}$A%T7<%y+@oWtjvqsIi&vr7V)7j3Zl>PT$##PW%Mu zpVnk_(~^OWEEw4$b0Y1n2>RCi@2}wEbqecjbuhJ*kK~?!6uzLd)b)?Uhw~q5PqD(2 z-B0#^lzlLl<;`wm!n222y6|#EOusV2 z)U(97O=y8gG0@B^pK)3m2KpqLOi%BcT+{_-#oaKp*YuAt1qUMU#_nZ*)#JiAk13K&1F{y$t~6ODL$6%lfz57{*GPV>$hUI$J}*2 z#gtBMlJo+x308dN08MSk20FoL`j2HEAX!7DNL`o(OP}WSW(yXgCGt3dWuSzG$f@)} zIbfVvZhHUM%;!_8@?y{H=q(Mas~8QXsOMmZ&rB;@KZ9Qef)ONE;%;A+;7q1tcC(wZ z4kPeo0Y~mNFU@V7!?~ldSlU7eG#dLAeKk^U}-2^_rekkyc?IM63{dz;?@TZ z$YSRese*(MBzF3ey8N6L zfZ(RW=!yo%&R1CJzKV0C0c#3ccYKCSx&t*bj=1jQBc&SE6yw0QtUA)|Mm!WKr@BEU4xEZ<$GMCu@CV7!SML&kM^IK z2)8*#{d@HLfpDNre+0pwaX-7%58N`%yZ+0weg^)mn;*byO#h;ypG*AAw;v^PY4oGT X;qUB&+W;AlSAeuflEzMf?-}|pl<4e` literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/test1.ods b/pandas/tests/io/data/test1.ods new file mode 100644 index 0000000000000000000000000000000000000000..5dc0e83456264f20f3aec48fc059fa0105782727 GIT binary patch literal 4440 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 OFDK_^CUVpM_WlQNTI$OH literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/test2.ods b/pandas/tests/io/data/test2.ods new file mode 100644 index 0000000000000000000000000000000000000000..2a90db839026b57eb35a1f5c2120b8a257ea416f GIT binary patch literal 2877 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 literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/test4.ods b/pandas/tests/io/data/test4.ods new file mode 100644 index 0000000000000000000000000000000000000000..c73a20d8b05621dfdd301f16a975576ad6116d21 GIT binary patch literal 2992 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 literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/test5.ods b/pandas/tests/io/data/test5.ods new file mode 100644 index 0000000000000000000000000000000000000000..5872e2624d0330b308defb8fcf5af335e4cc92f7 GIT binary patch literal 2906 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 literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/test_converters.ods b/pandas/tests/io/data/test_converters.ods new file mode 100644 index 0000000000000000000000000000000000000000..0216fb16311d8817a64e1dcd2c648fa07c6cc0db GIT binary patch literal 3287 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 HTmb$B5394} literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/test_index_name_pre17.ods b/pandas/tests/io/data/test_index_name_pre17.ods new file mode 100644 index 0000000000000000000000000000000000000000..56638c983d94409f5726581d30478cc2d70a6eae GIT binary patch literal 3699 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 literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/test_multisheet.ods b/pandas/tests/io/data/test_multisheet.ods new file mode 100644 index 0000000000000000000000000000000000000000..39058e67b4d5bd7b32a66018a2c78dc9aa256328 GIT binary patch literal 3797 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 KDIYTZnEeNEl)i8P literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/test_squeeze.ods b/pandas/tests/io/data/test_squeeze.ods new file mode 100644 index 0000000000000000000000000000000000000000..10ccf0da2693e7b612ece6b9ae28b4c2fd811b05 GIT binary patch literal 3218 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 literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/test_types.ods b/pandas/tests/io/data/test_types.ods new file mode 100644 index 0000000000000000000000000000000000000000..c9a82bfff810bca9bf129961f8eb0c8a72ef244c GIT binary patch literal 3489 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 R;TwADB}`3CokN!Z`~&E6gN6VA literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/testmultiindex.ods b/pandas/tests/io/data/testmultiindex.ods new file mode 100644 index 0000000000000000000000000000000000000000..b7f03900e6617408e0296cef406140b1e408146d GIT binary patch literal 5575 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 literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/testskiprows.ods b/pandas/tests/io/data/testskiprows.ods new file mode 100644 index 0000000000000000000000000000000000000000..443602a2c3f988d746d8dbdd2dd19a5e5784a243 GIT binary patch literal 3235 zcmZ`+2UJsA77ZP#0-0JaO3StZ@Omt?JGtQs$)?M$Gwa;DaytB{Q4+dpmWCQ%u!~xM5oj5RU@nf*k zhA-L|i6{6Y;r{*@v=e4t1hBQsB&oWuh09M)4%Z-2) z^I;cwpQZB09zzQs4YhmMHZuaqXIYGH&TonGIa6JOo5Q;D$hs{mUbT%{?qPO`Dq(qf z%6I~KzOrk~QF7N|4ZK5QS10u-8_)2m;b`YQa3NUxGR2GHGmavLg)e|ybe*+5>xaZ9 z59w~gA?61>h9VpBygGwE^u~gzr{i(lZL9dw!1B|u( zx?&n`7kzc?1@U#Pclne8J#j3T#jG7T{mod1TrL!-639*2QqfF6HqCh-rO{Z8Tt>4f zAwWo74D)mE!-`Kg2z*uYcp`4bKH=6!r+Qv=#vvK zJLD80U)DSslQ?y)r=II-A}5GzkE!mx((y#!b(@V>*Pv#z^=^wf+1oK2E3nZy*EiLA ztX`sZ?-%Etna~+TGsks_Gm7l9UB$<%qs#(bUZkV^@G8h!z0``lKupc^wWoAEsl3>bR6 z8YbWGc37a^$Eu}+-Mfu(4S+9AQ&!`uPb}u8=o;98*+AnlfqhP+X&0S@pTEscA`$kQ zaueL8nD-kk^~4+{}iB3z_o*?>O@`$sRNUA006O{+r@a zthoMoMgZWeAOJvPIl#*gi~ms*XdK^mvh-WgJl-X^apM%e3`vztWSWV;)EcG`>fQL3 z$=f{wg-st?!6uB(utCMVV#G zcD=qQu0nfII2y$pAF;k)*oHjmig~rTE~+~lHZD^8&W0Q0ibUYi-6H)Qmk+_mRhp0zSD@`k6TT_=WH}TQC-#0F-ZgHFZOMi`{W&)5RlyvfEn18X!3%LhmkC}nct1`Bc zl7#`?FrPp*xzqMV^gHFxiPf?0HKipds>-zCm5V~WZ`wERI+~Y~Op7g~3T445^N-eA ztSGH$@?N-Ns+}Fyo=(JaGT*k#S;sp>LNy zb+?qw+yz#RZs7XEGS6hWlC)N2$81R;>wc4bl9a!*%T`2uL~*pT7`OBE9j5%h^>x=KM`0mrp-)t??22^(?zm zy(`nNgF$Mph{in+?xrrmjBrA+Tv|J83V@!1~-C3w^py+luR6U4JcaGeVbWW=QN2KY@&OLvV zy|xgi&*<&Gg|=xTIWvA>EYCO`w1fq1mK}_N-2KTW-`D;Awq_BBxIez8odD?p0Djur z(!}0K+1$!h#TSl6qma0t)vG$$$cI6d!-Nt&-y32O+^8s%za1L!8Sr>Yo!M1W((>u7 zz7;$**ybEb&s<<9oNz-sv)W1}Tw%(p27cHk2|; z_5YC)<4PJKF{^Z#wb9+?9$_u5Nzu@Ta<`0P4erf)@S8LSiTGZ+gr~b^hDi*;o$7MOH56hS z(bM_XTyc*;qJ$TGCIFKglx%V%5RdjMi#XJW_q{^x`axXd=LRE!M$?wZLOZyh*GoX+ zfU<%gmY$TDQY`kY8c#JH>Sk~s1N%FZJWy%vY6^0t;#bv7N16=k7V)G``I@wj#`2gw zmBm#7`Lk+3rKd7|2+ohp>j<;+*@D2@h08|;Pkc_f0HvQWjSPY0Hx$aMFfgSFw$TjB zH6@SXszI?5D)Ya)CLO)^mM(uwoZnsio?^dTd_G3|&G=Ws!JtgENC5y$wC4|6Li+v? H6M%mJp8}3h literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/times_1900.ods b/pandas/tests/io/data/times_1900.ods new file mode 100644 index 0000000000000000000000000000000000000000..79e031c721ea34fc6097556d7a8d427f72f236aa GIT binary patch literal 3181 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 literal 0 HcmV?d00001 From 3bcc1b79ad10174d3cb573ac5637b55f5957a6ff Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sat, 29 Jun 2019 11:59:31 -0500 Subject: [PATCH 26/55] Updated tests --- pandas/tests/io/excel/conftest.py | 2 +- pandas/tests/io/excel/test_readers.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/pandas/tests/io/excel/conftest.py b/pandas/tests/io/excel/conftest.py index 935db254bd2e5..dd96fb2366152 100644 --- a/pandas/tests/io/excel/conftest.py +++ b/pandas/tests/io/excel/conftest.py @@ -30,7 +30,7 @@ def df_ref(): return df_ref -@pytest.fixture(params=['.xls', '.xlsx', '.xlsm']) +@pytest.fixture(params=['.xls', '.xlsx', '.xlsm', '.ods']) def read_ext(request): """ Valid extensions for reading Excel files. diff --git a/pandas/tests/io/excel/test_readers.py b/pandas/tests/io/excel/test_readers.py index 579f39e21d3c1..e5846a783f926 100644 --- a/pandas/tests/io/excel/test_readers.py +++ b/pandas/tests/io/excel/test_readers.py @@ -40,6 +40,8 @@ class TestReaders: not td.safe_import("xlrd"), reason="no xlrd")), pytest.param('openpyxl', marks=pytest.mark.skipif( not td.safe_import("openpyxl"), reason="no openpyxl")), + pytest.param("odf", marks=pytest.mark.skipif( + not td.safe_import("odf"), reason="no odfpy")), pytest.param(None, marks=pytest.mark.skipif( not td.safe_import("xlrd"), reason="no xlrd")), ]) @@ -49,6 +51,11 @@ def cd_and_set_engine(self, request, datapath, monkeypatch, read_ext): """ if request.param == 'openpyxl' and read_ext == '.xls': pytest.skip() + if request.param == 'odf' and read_ext != '.ods': + pytest.skip() + if read_ext == ".ods" and request.param != "odf": + pytest.skip() + func = partial(pd.read_excel, engine=request.param) monkeypatch.chdir(datapath("io", "data")) monkeypatch.setattr(pd, 'read_excel', func) @@ -733,13 +740,20 @@ class TestExcelFileRead: not td.safe_import("xlrd"), reason="no xlrd")), pytest.param('openpyxl', marks=pytest.mark.skipif( not td.safe_import("openpyxl"), reason="no openpyxl")), + pytest.param("odf", marks=pytest.mark.skipif( + not td.safe_import("odf"), reason="no odfpy")), pytest.param(None, marks=pytest.mark.skipif( not td.safe_import("xlrd"), reason="no xlrd")), ]) - def cd_and_set_engine(self, request, datapath, monkeypatch): + def cd_and_set_engine(self, request, datapath, monkeypatch, read_ext): """ Change directory and set engine for ExcelFile objects. """ + if request.param == 'odf' and read_ext != '.ods': + pytest.skip() + if read_ext == ".ods" and request.param != "odf": + pytest.skip() + func = partial(pd.ExcelFile, engine=request.param) monkeypatch.chdir(datapath("io", "data")) monkeypatch.setattr(pd, 'ExcelFile', func) From 15e69eb33ade04802adf0491f34aaff2937ff996 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sat, 29 Jun 2019 12:13:36 -0500 Subject: [PATCH 27/55] convert_float handling --- pandas/io/excel/_odfreader.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 5ff7a5ce30ca7..09a61d285440f 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -57,7 +57,7 @@ def get_sheet_by_name(self, name: str): raise ValueError("sheet {name} not found".format(name)) - def get_sheet_data(self, sheet, convert_float): + def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: """Parse an ODF Table into a list of lists """ from odf.table import TableCell, TableRow @@ -71,7 +71,7 @@ def get_sheet_data(self, sheet, convert_float): empty_cells = 0 table_row = [] for j, sheet_cell in enumerate(sheet_cells): - value = self._get_cell_value(sheet_cell) + value = self._get_cell_value(sheet_cell, convert_float) column_repeat = self._get_cell_repeat(sheet_cell) if len(sheet_cell.childNodes) == 0: @@ -129,13 +129,23 @@ def _is_empty_row(self, row): return True - def _get_cell_value(self, cell): + def _get_cell_value(self, cell, convert_float: bool) -> Scalar: from odf.namespaces import OFFICENS cell_type = cell.attributes.get((OFFICENS, 'value-type')) if cell_type == 'boolean': cell_value = cell.attributes.get((OFFICENS, 'boolean')) return bool(cell_value) - elif cell_type in ('float', 'percentage'): + if cell_type is None: + return '' # compat with xlrd + elif cell_type == 'float': + # GH5394 + cell_value = float(cell.attributes.get((OFFICENS, 'value'))) + if convert_float: + val = int(cell_value) + if val == cell_value: + return val + return cell_value + elif cell_type == 'percentage': cell_value = cell.attributes.get((OFFICENS, 'value')) return float(cell_value) elif cell_type == 'string': @@ -149,8 +159,6 @@ def _get_cell_value(self, cell): elif cell_type == 'time': cell_value = cell.attributes.get((OFFICENS, 'time-value')) return(pandas_isoduration_compatibility(cell_value)) - elif cell_type is None: - return None else: raise ValueError('Unrecognized type {}'.format(cell_type)) From 9584753bea4ada57b090018ff33a410d4217c5eb Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sat, 29 Jun 2019 22:58:11 -0500 Subject: [PATCH 28/55] Fixed missing value handling --- pandas/io/excel/_odfreader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 09a61d285440f..f53b7f9e95b97 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -78,7 +78,7 @@ def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: empty_cells += column_repeat else: if empty_cells > 0: - table_row.extend([None] * empty_cells) + table_row.extend([''] * empty_cells) empty_cells = 0 table_row.extend([value] * column_repeat) @@ -91,7 +91,7 @@ def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: else: if empty_rows > 0: # add blank rows to our table - table.extend([[None]] * empty_rows) + table.extend([['']] * empty_rows) empty_rows = 0 table.append(table_row) From 9dc34f44720573e4432935b91b066335a898c524 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sat, 29 Jun 2019 23:22:09 -0500 Subject: [PATCH 29/55] Fixed error handling --- pandas/io/excel/_odfreader.py | 6 +++++- pandas/tests/io/data/test3.ods | Bin 2891 -> 2889 bytes 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index f53b7f9e95b97..19bb0ee537338 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -98,7 +98,7 @@ def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: # Make our table square for row in table: if len(row) < max_row_len: - row.extend([None] * (max_row_len - len(row))) + row.extend([''] * (max_row_len - len(row))) return table @@ -140,6 +140,10 @@ def _get_cell_value(self, cell, convert_float: bool) -> Scalar: elif cell_type == 'float': # GH5394 cell_value = float(cell.attributes.get((OFFICENS, 'value'))) + + if cell_value == 0. and str(cell) != cell_value: # NA handling + return str(cell) + if convert_float: val = int(cell_value) if val == cell_value: diff --git a/pandas/tests/io/data/test3.ods b/pandas/tests/io/data/test3.ods index 2b6dc6ed4ccc1c389deeaa3a910446e686ff384e..dc78781caa6e9a2e8d8d3ba8afae8815d155266a 100644 GIT binary patch delta 1337 zcmV-91;+Zz7ReTnVg=%HrNC~HXC;3*DUt|La%jpR1(#4*){0*uOImh%8bRb#X?EipciV;!~N$^ zcZxAiXx%C@_Eg&3*cU-f2H~WsEj|b^9<{6$Hr&(__Y}mWRs)sEr(PJ>K6QT(gwR{v zV<8KFOV4(zx3gX5_HAr+_Qp%^R^!uFXJ-p2FWs5i>U=gLXDTPEAF$;B_A<;_aQW1r zqqRd_6sOZ^ce>mu-4)gC?QP+;C=RM6lie_C;jLOyP(j^LO;PQtBH7BZKTy53!WLWn z1|RbbtlGv3F#VuC#kR&!3}AoRqbyQnLGwMjPYh-X*vl}Xc}s~f_jjS{NeuJ{W_t!V z=o=RNpgHWsb5}=`o{N`RQ{3MbG2av-MUHYG5E2DI>+lg4XqD_|301C)viPm%GUw9L zj_SPS9k&K{7rE3c^AIz2d zY@H7p)>~T+cOd8hamxu7Il$JzeZiwuJrI_ljH&v7$y2A*C|j7cP(UrKu`~nLz&+PZ zOaLpo3=N*q{G#-KsY;J`sBfNA=*-R7X>zzm7z+7N?>0)%^TGns`M`$%fyS$9nDKH^ zoj65(Upa;JYB!|Nm;ZmCnry=;RxC*K=rkUVfLXGQ-+Ay1cpNGVnht(=hhz>;8tV zR3B%_;YQI%#bFjbw^Xm(oQ~k3L5IotFoEPy#}^ng2>3;d!+ zyV`=GuO?_0HcNPc13lIqY@Jn5R=K-$7|haxvOw&26oN8%ipt&f+FtYSILjl=-J1zp z2WzeU>nq{GP1GJh1Pki?pI#KUC9X zXI94By5wU3Z#sn$OE;GoujOU{PB(~QVdzYR(9z@=8~mqZYw98$|d)n*FB-UJ~S46Lx{$^3iFI%1WtKv+@``Ws|T{g zC?@H&wBA95j}Y;7eR)y(_1Y*9+N)gyE|CV3M6G|Ai(*(+co&@TGMXryY#@rHgx`Xj zDB>I~dh21*N&T1IVb8f*RroWdt)O(f3pVp%L$#t{p-UMl&p5?n&Vx1X>jdBL_u2lc zC-9;uXurj1yz{K08Lp@6JNLTmGi<;?kjaPo(Wu~~U#F+OBFgiOq0{jGpuo~wY1Pik z)V6>AI+Ra1%UMy0qT>k3OE9U2a}-( v7nA=A5F3EAU)o*)004{v000O8000000000000000kCQ438wL{!00000;2DV7 delta 1360 zcmV-W1+V(a7RwfpVg+w71ypa5XC;4SL1H0F4lR1@sr>+oqGTo%DUeib_v<^{v@}kk zTXTrv+2_rhp&y@5Jx|`K(oBj96}ucIR1n!P(N*Zz&+q92O0>bE!CVSjp)=L!`RVhg zN7=TF(6W(a?5QxRHfK(C0^x)%4X!yDk4lshYo<$qdkSJw%7KdLQ!b24m)d^|eCVz2 zp^$~Yp(nG|+uAO3yEe8uJL9EyqwsO7v$X}3m+r0H>U`2XZDmSiKVZWE>}8k}XX3R& z2V;gZ%Z|t6?s&74s>_P~exErl3WI9MWH*e8JFA9dlv6uYofW$xi?(9yYpS+Z*kFU- z;iKPxRohqrrXRE?+pRHV3$TA&qa;vdLGx?$6dBw~U~h{FO&dzMetPs(Phy}yFxxS> zLSHdwwPLUnZ(SKoddAPQCVP6!LcS?Pj2z@HASCjDmj1&p&??!F9I9LyCE;7oM9PGr z9aU+=I%YKNF0!dt=00ZZ$faE+_(oeia$`5#w%B6dire0lP|l;q4%2@JOYJ6C$d%^3 zX6tlNu-?irxC34Xh#N*QPXV?L?h77`?18WZC9N!LOkO)BN72Bfxddt%g@qol0`A#v zLIPOPWoYmW=2yAY|0A|rPd|zT>k4f1w&MTxPXAMdwNyPT* zuHM(pUC_t@6J~(7MV@~NGHsN6g&so$OK~qskE}6=3gx@JxOvz!;rSnwaTvG7eX)nh zF8n^ulERImFOtD5dS<9vxmg{-Lxt+mI3fhZRv^uL=75BK3hRG0k<5cRZS;UUnsR)W zqgidi&=(W53!5dpz=0mC4z|unC@bIHSPW)qEe#O+f`Zc)o}zqrzqVJbJIwMxbNgn( z*1=jU_xcKWa3i$`5P^fNUnns;AqTABBRnB`-jxHz4cvqkwSugb5+eWI6|{7qv6|bv zb(x#0zkmWnnQ32ssiaWL|0&ata8={Fp1`&R7iO-#Cs$xy53gNBexh6u+DI$P^+PdD zc4lR`txG<7@TOB3u(Wdt@fxNF;Bl3JrDyKBGjru)dDt4X2OqSvC1y#A5m@Pzwwn^a zt)9p&Mj;BTgmD&1e1w3P)%8{3)@v<+D<|tM*hsd25Cv+@Y~Vwy#I?8FiJ$|wVhe#s z$=wd@gz$4U??(@n&g#E(r#)wtEb(`YJ5K4Y_9k(DLzSdppxZJ~mN1IPJr73PpOd_4 znxwhu0hZ@E?YkI_cb-Kw!_{a4{5oKADqEqqlAi>ak zVPtK8L~K)kAHpXr&C)WY*0%D1^@s6 S00jUA03ZPX02K-V0002>4tw7K From 5e32f6da3f465a5cbac22207ed8ef42556f5ce50 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sat, 29 Jun 2019 23:35:09 -0500 Subject: [PATCH 30/55] Fixed bool handling --- pandas/io/excel/_odfreader.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 19bb0ee537338..5da1e4363e5f1 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -133,8 +133,9 @@ def _get_cell_value(self, cell, convert_float: bool) -> Scalar: from odf.namespaces import OFFICENS cell_type = cell.attributes.get((OFFICENS, 'value-type')) if cell_type == 'boolean': - cell_value = cell.attributes.get((OFFICENS, 'boolean')) - return bool(cell_value) + if str(cell) == "TRUE": + return True + return False if cell_type is None: return '' # compat with xlrd elif cell_type == 'float': From 6360c07f4a1ae4e6182355fbb68e67093c996059 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sat, 29 Jun 2019 23:36:57 -0500 Subject: [PATCH 31/55] Skip missing file on master --- pandas/tests/io/excel/test_readers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pandas/tests/io/excel/test_readers.py b/pandas/tests/io/excel/test_readers.py index 1db2fb1a08fdc..1ce10199175a6 100644 --- a/pandas/tests/io/excel/test_readers.py +++ b/pandas/tests/io/excel/test_readers.py @@ -441,6 +441,9 @@ def test_bad_engine_raises(self, read_ext): @tm.network def test_read_from_http_url(self, read_ext): + if read_ext == '.ods': # TODO: remove once on master + pytest.skip() + url = ('https://raw.github.com/pandas-dev/pandas/master/' 'pandas/tests/io/data/test1' + read_ext) url_table = pd.read_excel(url) From 42272683b031c10b3a6977698711f790871bf586 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sat, 29 Jun 2019 23:49:26 -0500 Subject: [PATCH 32/55] datetime compat --- pandas/io/excel/_odfreader.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 5da1e4363e5f1..807e84c469cd0 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -162,19 +162,6 @@ def _get_cell_value(self, cell, convert_float: bool) -> Scalar: cell_value = cell.attributes.get((OFFICENS, 'date-value')) return pd.Timestamp(cell_value) elif cell_type == 'time': - cell_value = cell.attributes.get((OFFICENS, 'time-value')) - return(pandas_isoduration_compatibility(cell_value)) + return pd.to_datetime(str(cell)).time() else: raise ValueError('Unrecognized type {}'.format(cell_type)) - - -def pandas_isoduration_compatibility(duration): - """Libreoffice returns durations without any day attributes - - For example PT3H45M0S. The current pandas Timedelta - parse requires the presence of a day component. - Workaround for https://github.com/pandas-dev/pandas/issues/25422 - """ - if duration.startswith('PT'): - duration = 'P0DT' + duration[2:] - return pd.Timedelta(duration) From 80607b084b708b84286136d6bf716dd0a26127c1 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sat, 29 Jun 2019 23:55:04 -0500 Subject: [PATCH 33/55] fixed row repeat --- pandas/io/excel/_odfreader.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 807e84c469cd0..db2df1cf9d489 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -93,7 +93,8 @@ def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: # add blank rows to our table table.extend([['']] * empty_rows) empty_rows = 0 - table.append(table_row) + for _ in range(row_repeat): + table.append(table_row) # Make our table square for row in table: From 43f7160fa2e97be33460c7fedc90185fda4184a9 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sun, 30 Jun 2019 00:17:11 -0500 Subject: [PATCH 34/55] multiindex handling --- pandas/io/excel/_odfreader.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index db2df1cf9d489..acd9b479aec91 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -72,15 +72,15 @@ def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: table_row = [] for j, sheet_cell in enumerate(sheet_cells): value = self._get_cell_value(sheet_cell, convert_float) - column_repeat = self._get_cell_repeat(sheet_cell) + column_span = self._get_column_span(sheet_cell) if len(sheet_cell.childNodes) == 0: - empty_cells += column_repeat + empty_cells += column_span else: if empty_cells > 0: table_row.extend([''] * empty_cells) empty_cells = 0 - table_row.extend([value] * column_repeat) + table_row.extend([value] * column_span) if max_row_len < len(table_row): max_row_len = len(table_row) @@ -114,9 +114,10 @@ def _get_row_repeat(self, row): return 1 return int(repeat) - def _get_cell_repeat(self, cell): + def _get_column_span(self, cell): + # TODO: seems like row spans need to be handled as well... from odf.namespaces import TABLENS - repeat = cell.attributes.get((TABLENS, 'number-columns-repeated')) + repeat = cell.attributes.get((TABLENS, 'number-columns-spanned')) if repeat is None: return 1 return int(repeat) @@ -161,7 +162,7 @@ def _get_cell_value(self, cell, convert_float: bool) -> Scalar: return float(cell_value) elif cell_type == 'date': cell_value = cell.attributes.get((OFFICENS, 'date-value')) - return pd.Timestamp(cell_value) + return pd.to_datetime(cell_value) elif cell_type == 'time': return pd.to_datetime(str(cell)).time() else: From cbbc6533c3dd89ef322e7091269ffd433726bca3 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sun, 30 Jun 2019 10:14:14 -0500 Subject: [PATCH 35/55] Handled horizontally merged cells --- pandas/io/excel/_odfreader.py | 43 ++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index acd9b479aec91..ad160799c552c 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -66,21 +66,43 @@ def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: table = [] empty_rows = 0 max_row_len = 0 + row_spans = {} # type: Dict[int, int] + for i, sheet_row in enumerate(sheet_rows): sheet_cells = sheet_row.getElementsByType(TableCell) empty_cells = 0 table_row = [] + for j, sheet_cell in enumerate(sheet_cells): + # Handle vertically merged cells; only works with first column + if row_spans.get(j, 0) > 1: + table_row.append('') + row_spans[j] = row_spans[j] - 1 + value = self._get_cell_value(sheet_cell, convert_float) + column_repeat = self._get_column_repeat(sheet_cell) column_span = self._get_column_span(sheet_cell) + row_span = self._get_row_span(sheet_cell) + + if row_span > 1: + if j > 0: + raise ValueError( + "The odf reader only supports vertical " + "merging in the initial column") + else: + row_spans[j] = row_span if len(sheet_cell.childNodes) == 0: - empty_cells += column_span + empty_cells += column_repeat else: if empty_cells > 0: table_row.extend([''] * empty_cells) empty_cells = 0 - table_row.extend([value] * column_span) + table_row.extend([value] * column_repeat) + + # horizontally merged cells should only show first value + if column_span > 1: + table_row.extend([''] * (column_span - 1)) if max_row_len < len(table_row): max_row_len = len(table_row) @@ -114,8 +136,23 @@ def _get_row_repeat(self, row): return 1 return int(repeat) + def _get_column_repeat(self, cell): + from odf.namespaces import TABLENS + repeat = cell.attributes.get((TABLENS, 'number-columns-repeated')) + if repeat is None: + return 1 + return int(repeat) + + def _get_row_span(self, cell): + """For handling cells merged vertically.""" + from odf.namespaces import TABLENS + repeat = cell.attributes.get((TABLENS, 'number-rows-spanned')) + if repeat is None: + return 1 + return int(repeat) + def _get_column_span(self, cell): - # TODO: seems like row spans need to be handled as well... + """For handling cells merged horizontally.""" from odf.namespaces import TABLENS repeat = cell.attributes.get((TABLENS, 'number-columns-spanned')) if repeat is None: From 1227216de8038a52b4a8ce182080430d69bc2651 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sun, 30 Jun 2019 10:23:49 -0500 Subject: [PATCH 36/55] Converted to pytest idiom --- pandas/tests/io/excel/test_odf.py | 46 ++++++++++++++++--------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/pandas/tests/io/excel/test_odf.py b/pandas/tests/io/excel/test_odf.py index 43ad5cdfa7893..c701a985d2e64 100644 --- a/pandas/tests/io/excel/test_odf.py +++ b/pandas/tests/io/excel/test_odf.py @@ -1,3 +1,4 @@ +import functools from collections import OrderedDict import numpy as np @@ -9,9 +10,15 @@ pytest.importorskip("odf") -def test_read_types(datapath): - path = datapath("io", "data", "datatypes.ods") - sheet = pd.read_excel(path, header=None, engine='odf') +@pytest.fixture(autouse=True) +def cd_and_set_engine(monkeypatch, datapath): + func = functools.partial(pd.read_excel, engine="odf") + monkeypatch.setattr(pd, 'read_excel', func) + monkeypatch.chdir(datapath("io", "data")) + + +def test_read_types(): + sheet = pd.read_excel("datatypes.ods", header=None) expected = pd.DataFrame( [[1.0], @@ -28,31 +35,29 @@ def test_read_types(datapath): tm.assert_equal(sheet, expected) -def test_read_invalid_types_raises(datapath): +def test_read_invalid_types_raises(): # the invalid_value_type.ods required manually editing # of the included content.xml file - path = datapath("io", "data", "invalid_value_type.ods") with pytest.raises(ValueError, match="Unrecognized type awesome_new_type"): - pd.read_excel(path, header=None, engine='odf') + pd.read_excel("invalid_value_type.ods", header=None) -def test_read_lower_diagonal(datapath): +def test_read_lower_diagonal(): # Make sure we can parse: # 1 # 2 3 # 4 5 6 # 7 8 9 10 - path = datapath("io", "data", "lowerdiagonal.ods") - sheet = pd.read_excel(path, 'Sheet1', - index_col=None, header=None, engine='odf') + + sheet = pd.read_excel("lowerdiagonal.ods", 'Sheet1', + index_col=None, header=None) assert sheet.shape == (4, 4) -def test_read_headers(datapath): - path = datapath("io", "data", "headers.ods") - sheet = pd.read_excel(path, 'Sheet1', index_col=0, engine='odf') +def test_read_headers(): + sheet = pd.read_excel("headers.ods", 'Sheet1', index_col=0) expected = pd.DataFrame.from_dict(OrderedDict([ ("Header", ["Row 1", "Row 2"]), @@ -71,12 +76,11 @@ def test_read_headers(datapath): assert pd.isnull(value) -def test_read_writer_table(datapath): +def test_read_writer_table(): # Also test reading tables from an text OpenDocument file # (.odt) - path = datapath("io", "data", "writertable.odt") - table = pd.read_excel(path, 'Table1', index_col=0, engine='odf') + table = pd.read_excel("writertable.odt", 'Table1', index_col=0) assert table.shape == (3, 3) expected = pd.DataFrame.from_dict(OrderedDict([ @@ -93,9 +97,8 @@ def test_read_writer_table(datapath): assert pd.isnull(table["Unnamed: 2"][i]) -def test_blank_row_repeat(datapath): - path = datapath("io", "data", "blank-row-repeat.ods") - table = pd.read_excel(path, 'Value', engine='odf') +def test_blank_row_repeat(): + table = pd.read_excel("blank-row-repeat.ods", 'Value') assert table.shape == (14, 2) assert table['value'][7] == 9.0 @@ -103,9 +106,8 @@ def test_blank_row_repeat(datapath): assert not pd.isnull(table['value'][11]) -def test_runlengthencoding(datapath): - path = datapath("io", "data", "runlengthencoding.ods") - sheet = pd.read_excel(path, 'Sheet1', header=None, engine='odf') +def test_runlengthencoding(): + sheet = pd.read_excel("runlengthencoding.ods", 'Sheet1', header=None) assert sheet.shape == (5, 3) # check by column, not by row. assert list(sheet[0]) == [1.0, 1.0, 2.0, 2.0, 2.0] From 696ed5dc256f4d47aca452e171f6a9b68b502feb Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sun, 30 Jun 2019 10:28:19 -0500 Subject: [PATCH 37/55] Test idiom cleanup --- pandas/tests/io/excel/test_odf.py | 62 +++++-------------------------- 1 file changed, 9 insertions(+), 53 deletions(-) diff --git a/pandas/tests/io/excel/test_odf.py b/pandas/tests/io/excel/test_odf.py index c701a985d2e64..ab32219bde68f 100644 --- a/pandas/tests/io/excel/test_odf.py +++ b/pandas/tests/io/excel/test_odf.py @@ -17,24 +17,6 @@ def cd_and_set_engine(monkeypatch, datapath): monkeypatch.chdir(datapath("io", "data")) -def test_read_types(): - sheet = pd.read_excel("datatypes.ods", header=None) - - expected = pd.DataFrame( - [[1.0], - [1.25], - ['a'], - [pd.Timestamp(2003, 1, 2)], - [False], - [0.35], - [pd.Timedelta(hours=3, minutes=45), - pd.Timedelta(hours=17, minutes=53), - pd.Timedelta(hours=14, minutes=8)], - # though what should the value of a hyperlink be? - ['UBERON:0002101']]) - tm.assert_equal(sheet, expected) - - def test_read_invalid_types_raises(): # the invalid_value_type.ods required manually editing # of the included content.xml file @@ -56,45 +38,19 @@ def test_read_lower_diagonal(): assert sheet.shape == (4, 4) -def test_read_headers(): - sheet = pd.read_excel("headers.ods", 'Sheet1', index_col=0) - - expected = pd.DataFrame.from_dict(OrderedDict([ - ("Header", ["Row 1", "Row 2"]), - ("Column 1", [1.0, 2.0]), - ("Column 2", [3.0, 4.0]), - # Empty Column - ("Column 4", [7.0, 8.0]), - # Empty Column 2 - ("Column 6", [11.0, 12.0])])) - expected.set_index("Header", inplace=True) - columns = ["Column 1", "Column 2", "Column 4", "Column 6"] - tm.assert_equal(sheet[columns], expected) - empties = [None, 'None.1'] - for name in empties: - for value in sheet[name]: - assert pd.isnull(value) - - def test_read_writer_table(): # Also test reading tables from an text OpenDocument file # (.odt) + index = pd.Index(["Row 1", "Row 2", "Row 3"], name="Header") + expected = pd.DataFrame([ + [1, np.nan, 7], + [2, np.nan, 8], + [3, np.nan, 9], + ], index=index, columns=["Column 1", "Unnamed: 2", "Column 3"]) + + result = pd.read_excel("writertable.odt", 'Table1', index_col=0) - table = pd.read_excel("writertable.odt", 'Table1', index_col=0) - - assert table.shape == (3, 3) - expected = pd.DataFrame.from_dict(OrderedDict([ - ("Header", ["Row 1", "Row 2", "Row 3"]), - ("Column 1", [1.0, 2.0, 3.0]), - ("Unnamed: 2", [np.nan, np.nan, np.nan]), - ("Column 3", [7.0, 8.0, 9.0])])) - expected.set_index("Header", inplace=True) - columns = ["Column 1", "Column 3"] - tm.assert_equal(table[columns], expected[columns]) - - # make sure pandas gives a name to the unnamed column - for i in range(3): - assert pd.isnull(table["Unnamed: 2"][i]) + tm.assert_frame_equal(result, expected) def test_blank_row_repeat(): From 49fff9f64927532ae9c936d62575e5bd49462d78 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sun, 30 Jun 2019 10:30:35 -0500 Subject: [PATCH 38/55] Removed duplicative test files --- pandas/tests/io/data/datatypes.ods | Bin 10608 -> 0 bytes pandas/tests/io/data/headers.ods | Bin 8325 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 pandas/tests/io/data/datatypes.ods delete mode 100644 pandas/tests/io/data/headers.ods diff --git a/pandas/tests/io/data/datatypes.ods b/pandas/tests/io/data/datatypes.ods deleted file mode 100644 index 7f532b9260831a74c4b03dbf281bbcd69d1d0bc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10608 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 OmkAsIa7O)j_5T2;#F*#+ diff --git a/pandas/tests/io/data/headers.ods b/pandas/tests/io/data/headers.ods deleted file mode 100644 index 753c24762a9ec2940bd282476d1ece2cd4252281..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8325 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 From 7b0830442bbce4a954cba2fc075f23ae6cae2b27 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sun, 30 Jun 2019 10:33:35 -0500 Subject: [PATCH 39/55] Raised NotImplemented for vertical merging --- pandas/io/excel/_odfreader.py | 4 ++-- pandas/tests/io/data/raising_repeats.ods | Bin 0 -> 2929 bytes pandas/tests/io/excel/test_odf.py | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 pandas/tests/io/data/raising_repeats.ods diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index ad160799c552c..a2caa0311330c 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -86,8 +86,8 @@ def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: if row_span > 1: if j > 0: - raise ValueError( - "The odf reader only supports vertical " + raise NotImplementedError( + "The odf reader only supports vertical cell" "merging in the initial column") else: row_spans[j] = row_span diff --git a/pandas/tests/io/data/raising_repeats.ods b/pandas/tests/io/data/raising_repeats.ods new file mode 100644 index 0000000000000000000000000000000000000000..926caa99d070c1f4679a58651711f5d47426319e GIT binary patch literal 2929 zcmZ{m2{e>#8^^~sm}H$SEy|jGiA46DCX6LZBN|Nh!OYl35we5`QFxOr>kw*`L2t&+ zkbNB_B70&iN#4s-J4`a>af^dP|BLjVxvr4lDYeL0#; z)amW+jlcx^BH+HhUhd9tjJpp~HUQ}&;{!*#qh)-25l9ywXMb-55+j55MIqoWXg34` z1Et0U1Aspx2mmselK7Bx0Du{Fo1>l3m|!miS|-TbtJ%5(IU&d1!1Gy?i3R&eL`6J> z$sFa@=WMph*b5d{$TbQta4K?~o+%W8_{VmZ2O(Jdo-n;0-@Yb>ex!F&7m;K_ty+wf*hmc0OvWJ=%$D++~wh!m~+|^Fg zymUGBr>wkRTfr^+SRkKBP<*vo4`VRu|47!dcy)s_uUuYSzIMvb1WjQ!h;G*1<=ZuK( z(RDF;#20W{-7>yme$p@9FthFwR}Jg%_5BeWVq{ja`dy~egEPsTo)=KfCpA8DInCct zu^TIVwEtN4UmnJ`hRx z*K={WsXJLps3!Qh6Im%&ekPDa--@9HUa{?1RJ{VNAu6 zWZj6G6=QGk8Tt_k3Ct7gxayR)dis`UV#wGSn^tdiiMAF^+{&NNfcK)Vk;iXIK_MI5w++_wsVc+*xC4rPnRb+n2jBz9D&dYkktCklrL#4d{Gr*xHSC z37Bf?lT|F60`Q2juFqxCX{kWklRE7l{bIsoGPt1Bhdl&+twgm9pi7F9#Z zr#d|{Bzm_Zw=i{sG05cf7^jO@Tnaa1oF0pi^H9bRK6citgh|od>~!-(vHQgzyf>n* z^x1}+Y}C9kOf_vE2reRfcgj!A35M{GxUc)iAe@B>**dL`JTtsjxraL6(+icW@Jw{# zn~Awfn45}1xqUS)j=lX3-@G_H{HhLj?u09+&ce)-Uml+KV8!-C@Wv}NVrh^G_YpeC zV&QfQe^I}l4G_@?cxup7<5q(4HD%h%34wIO*9b%YZj_Z&0q2`d8K_nrgTP4619O2_ zE2>c*SCFmsA3t8(ZKXXhJ?VyT0Yw#{G^8@vBKCE8iSLKL1>d-18ulIsV;u;kA6S|J z30072M0epE0H?5bdvO}=}^^;f`xJ$X!^`s zBuxQVoc(wbebOgph6d%BNHt8yaZ7IcEVY7VgrdLjMU^k-n%f>1d9@6<#Ov87ypofU z?E^laV&cQjfzGfFEGFhuGk!D!md)0;jDKpk+5IE;ihhs{kasBAo#*^wNCfY_{qq{? zq>3S|W#aS(BuM4?jwNX$P-h&v?`gyX-WE{$pyoNZeqr%YMra_&6>z@Kq`=#PXjaoR z3<2*#v<%Dj#7XyVA8vhJ3{Lr4kI-QML`(Y&$)(KGzo(CqFaMeY*_1l$OhS7#@8(S( zP`Ujl*)g4L8>7?}Ff9OZf_gX6x6+n2HZ_p-h9li?A<#b=$34{cpp$3O--()P4AKmE zDJh!0f{oY)5Qi0kFeM?AcCv;kJSD)~C|A{(d;LyunDS$ssWQ5Bj?z=_76taWy0u7Y z+n@MSdg2)Lo;`DeLox+^J9L~UM^PYt0yJdY{Cap9A!-xOr5X{jBK&6jq0@1QHs=m{ z`o`q!8P(?rx{g^kcs}y0IW@(H`6JfTZ4Ay4+QY+}+MX&54?`2x%@rMKNP#bAl!Wv& z1mkLwPT377cwv_u2P0goMiw-+bVU{oe<-AqYxnHQHR;NpmXw-!d8~p%q39G-eU={R7_{yn1U4>%#(rtv zzg^G9_Gno*KULP(!Ol`3ljG4{4r_^*WxZYV@(ud;*~mr}<>8<+jrsm}?pzyRwPwEA zA6G-e)eodK!z#>B?uSIG-=;$-La3i-t2w-<8t~li)umr`?4ehG{OpMUqsZl z-za=#x|WE_b0j_ouUw*dDH9B?JUf@Brqyunl~IR8cSrP8}Y7Ee_A?f@&W%ek4ImBjsI9lDC9`>@AmMQ!ixI% z%Pjt``@Mnv(k)T{@4wpF@50~t_$6#+{1;k&5Al0l{R;7(T4q$s{IklS5C-7U3Ip{z LN3BBDpFaC96Lk^i literal 0 HcmV?d00001 diff --git a/pandas/tests/io/excel/test_odf.py b/pandas/tests/io/excel/test_odf.py index ab32219bde68f..0de582c041763 100644 --- a/pandas/tests/io/excel/test_odf.py +++ b/pandas/tests/io/excel/test_odf.py @@ -69,3 +69,9 @@ def test_runlengthencoding(): assert list(sheet[0]) == [1.0, 1.0, 2.0, 2.0, 2.0] assert list(sheet[1]) == [1.0, 2.0, 2.0, 2.0, 2.0] assert list(sheet[2]) == [1.0, 2.0, 2.0, 2.0, 2.0] + + +def test_raises_repeated_rows_not_in_col_0(): + with pytest.raises(NotImplementedError, + match="merging in the initial column"): + pd.read_excel("raising_repeats.ods") From 4d97d84249ba520e414758672c4106686cfc8c92 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sun, 30 Jun 2019 10:40:46 -0500 Subject: [PATCH 40/55] Table attribute access simplification --- pandas/io/excel/_odfreader.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index a2caa0311330c..07236882dd3a7 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -33,11 +33,10 @@ def load_workbook(self, filepath_or_buffer: FilePathOrBuffer): @property def sheet_names(self) -> List[str]: """Return a list of sheet names present in the document""" - from odf.namespaces import TABLENS from odf.table import Table tables = self.book.getElementsByType(Table) - return [t.attributes[(TABLENS, 'name')] for t in tables] + return [t.getAttribute("name") for t in tables] def get_sheet_by_index(self, index: int): from odf.table import Table @@ -45,14 +44,12 @@ def get_sheet_by_index(self, index: int): return tables[index] def get_sheet_by_name(self, name: str): - from odf.namespaces import TABLENS from odf.table import Table tables = self.book.getElementsByType(Table) - key = (TABLENS, "name") for table in tables: - if table.attributes[key] == name: + if table.getAttribute("name") == name: return table raise ValueError("sheet {name} not found".format(name)) From 59cdf0bfa2f883b50e79347409464627d5d92131 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sun, 30 Jun 2019 10:44:11 -0500 Subject: [PATCH 41/55] Typing and func cleanups --- pandas/io/excel/_odfreader.py | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 07236882dd3a7..124a336042ebb 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -122,41 +122,31 @@ def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: return table - def _get_row_repeat(self, row): + def _get_row_repeat(self, row) -> int: """Return number of times this row was repeated Repeating an empty row appeared to be a common way of representing sparse rows in the table. """ from odf.namespaces import TABLENS - repeat = row.attributes.get((TABLENS, 'number-rows-repeated')) - if repeat is None: - return 1 - return int(repeat) - def _get_column_repeat(self, cell): + return int(row.attributes.get((TABLENS, 'number-rows-repeated'), 1)) + + def _get_column_repeat(self, cell) -> int: from odf.namespaces import TABLENS - repeat = cell.attributes.get((TABLENS, 'number-columns-repeated')) - if repeat is None: - return 1 - return int(repeat) + return int(cell.attributes.get( + (TABLENS, 'number-columns-repeated'), 1)) - def _get_row_span(self, cell): + def _get_row_span(self, cell) -> int: """For handling cells merged vertically.""" from odf.namespaces import TABLENS - repeat = cell.attributes.get((TABLENS, 'number-rows-spanned')) - if repeat is None: - return 1 - return int(repeat) + return int(cell.attributes.get((TABLENS, 'number-rows-spanned'), 1)) - def _get_column_span(self, cell): + def _get_column_span(self, cell) -> int: """For handling cells merged horizontally.""" from odf.namespaces import TABLENS - repeat = cell.attributes.get((TABLENS, 'number-columns-spanned')) - if repeat is None: - return 1 - return int(repeat) + return int(cell.attributes.get((TABLENS, 'number-columns-spanned'), 1)) - def _is_empty_row(self, row): + def _is_empty_row(self, row) -> bool: """Helper function to find empty rows """ for column in row.childNodes: From 98d3ca74a19b55466f0384dc2c09a324f256f521 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sun, 30 Jun 2019 10:51:50 -0500 Subject: [PATCH 42/55] lint and isort --- pandas/io/excel/_odfreader.py | 7 +++---- pandas/tests/io/excel/test_odf.py | 1 - pandas/tests/io/excel/test_readers.py | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 124a336042ebb..a296460cb2190 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -1,11 +1,10 @@ -from typing import List +from typing import Dict, List -import pandas as pd +from pandas.compat._optional import import_optional_dependency +import pandas as pd from pandas._typing import FilePathOrBuffer, Scalar -from pandas.compat._optional import import_optional_dependency - from pandas.io.excel._base import _BaseExcelReader diff --git a/pandas/tests/io/excel/test_odf.py b/pandas/tests/io/excel/test_odf.py index 0de582c041763..bd39a58cd9d1d 100644 --- a/pandas/tests/io/excel/test_odf.py +++ b/pandas/tests/io/excel/test_odf.py @@ -1,5 +1,4 @@ import functools -from collections import OrderedDict import numpy as np import pytest diff --git a/pandas/tests/io/excel/test_readers.py b/pandas/tests/io/excel/test_readers.py index 1ce10199175a6..cb87ef0abc9fa 100644 --- a/pandas/tests/io/excel/test_readers.py +++ b/pandas/tests/io/excel/test_readers.py @@ -743,7 +743,7 @@ class TestExcelFileRead: pytest.param('openpyxl', marks=pytest.mark.skipif( not td.safe_import("openpyxl"), reason="no openpyxl")), pytest.param("odf", marks=pytest.mark.skipif( - not td.safe_import("odf"), reason="no odfpy")), + not td.safe_import("odf"), reason="no odfpy")), pytest.param(None, marks=pytest.mark.skipif( not td.safe_import("xlrd"), reason="no xlrd")), ]) @@ -754,7 +754,7 @@ def cd_and_set_engine(self, request, datapath, monkeypatch, read_ext): if request.param == 'odf' and read_ext != '.ods': pytest.skip() if read_ext == ".ods" and request.param != "odf": - pytest.skip() + pytest.skip() if request.param == 'openpyxl' and read_ext == '.xls': pytest.skip() From 6576af991bb24b8a05d56c064df9cd9d73ad9692 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sun, 30 Jun 2019 11:32:19 -0500 Subject: [PATCH 43/55] typing fixup --- pandas/io/excel/_odfreader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index a296460cb2190..70cd5933a9abb 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -59,7 +59,7 @@ def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: from odf.table import TableCell, TableRow sheet_rows = sheet.getElementsByType(TableRow) - table = [] + table = [] # type: List[List[Scalar]] empty_rows = 0 max_row_len = 0 row_spans = {} # type: Dict[int, int] @@ -67,7 +67,7 @@ def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: for i, sheet_row in enumerate(sheet_rows): sheet_cells = sheet_row.getElementsByType(TableCell) empty_cells = 0 - table_row = [] + table_row = [] # type: List[Scalar] for j, sheet_cell in enumerate(sheet_cells): # Handle vertically merged cells; only works with first column From 4dc1b514b7f739fa47b54016eb28891119a346e2 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Sun, 30 Jun 2019 13:48:14 -0500 Subject: [PATCH 44/55] Skip ods files for xlrd --- pandas/tests/io/excel/test_xlrd.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pandas/tests/io/excel/test_xlrd.py b/pandas/tests/io/excel/test_xlrd.py index 94e1435d4dfab..d749f0ec3e252 100644 --- a/pandas/tests/io/excel/test_xlrd.py +++ b/pandas/tests/io/excel/test_xlrd.py @@ -10,6 +10,12 @@ xlwt = pytest.importorskip("xlwt") +@pytest.fixture(autouse=True) +def skip_ods_files(read_ext): + if read_ext == ".ods": + pytest.skip("Not valid for xlrd") + + def test_read_xlrd_book(read_ext, frame): df = frame From 8ce45b45c4fedeeef872705e3cc12a6f465f9743 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Mon, 1 Jul 2019 06:36:45 -0500 Subject: [PATCH 45/55] Removed one-off tests --- pandas/tests/io/data/blank-row-repeat.ods | Bin 10894 -> 0 bytes pandas/tests/io/data/lowerdiagonal.ods | Bin 7528 -> 0 bytes pandas/tests/io/data/runlengthencoding.ods | Bin 7898 -> 0 bytes pandas/tests/io/excel/test_odf.py | 33 +-------------------- 4 files changed, 1 insertion(+), 32 deletions(-) delete mode 100644 pandas/tests/io/data/blank-row-repeat.ods delete mode 100644 pandas/tests/io/data/lowerdiagonal.ods delete mode 100644 pandas/tests/io/data/runlengthencoding.ods diff --git a/pandas/tests/io/data/blank-row-repeat.ods b/pandas/tests/io/data/blank-row-repeat.ods deleted file mode 100644 index e6712854eb5cd7dedaf0aa159903536e68585a6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10894 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 E0F)DlPXGV_ diff --git a/pandas/tests/io/data/runlengthencoding.ods b/pandas/tests/io/data/runlengthencoding.ods deleted file mode 100644 index f28355a635c5b75d6ad4a8db21a36a7bbf61bb2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7898 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 Date: Mon, 1 Jul 2019 07:11:45 -0500 Subject: [PATCH 46/55] Handled defusedxml warnings --- pandas/tests/io/excel/test_readers.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pandas/tests/io/excel/test_readers.py b/pandas/tests/io/excel/test_readers.py index 10c31d199a20d..57611f3078959 100644 --- a/pandas/tests/io/excel/test_readers.py +++ b/pandas/tests/io/excel/test_readers.py @@ -33,9 +33,20 @@ def ignore_xlrd_time_clock_warning(): @pytest.fixture(params=[ # Add any engines to test here - pytest.param('xlrd', marks=td.skip_if_no('xlrd')), - pytest.param('openpyxl', marks=td.skip_if_no('openpyxl')), - pytest.param(None, marks=td.skip_if_no('xlrd')), + # When defusedxml is installed it triggers deprecation warnings for + # xlrd and openpyxl, so catch those here + pytest.param('xlrd', marks=[ + td.skip_if_no('xlrd'), + pytest.mark.filterwarnings("ignore:.*(tree\\.iter|html argument)"), + ]), + pytest.param('openpyxl', marks=[ + td.skip_if_no('openpyxl'), + pytest.mark.filterwarnings("ignore:.*html argument"), + ]), + pytest.param(None, marks=[ + td.skip_if_no('xlrd'), + pytest.mark.filterwarnings("ignore:.*(tree\\.iter|html argument)"), + ]), pytest.param("odf", marks=td.skip_if_no("odf")), ]) def engine(request): From 3e0d7582ca284acd7a9397d76242fb1944ceb10c Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Mon, 1 Jul 2019 07:15:55 -0500 Subject: [PATCH 47/55] Updated assert_warnings funcs to allow DeprecationWarnings --- pandas/tests/io/excel/test_readers.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pandas/tests/io/excel/test_readers.py b/pandas/tests/io/excel/test_readers.py index 57611f3078959..ae69c2302e60a 100644 --- a/pandas/tests/io/excel/test_readers.py +++ b/pandas/tests/io/excel/test_readers.py @@ -79,14 +79,16 @@ def test_usecols_int(self, read_ext, df_ref): # usecols as int with tm.assert_produces_warning(FutureWarning, - check_stacklevel=False): + check_stacklevel=False, + raise_on_extra_warnings=False): with ignore_xlrd_time_clock_warning(): df1 = pd.read_excel("test1" + read_ext, "Sheet1", index_col=0, usecols=3) # usecols as int with tm.assert_produces_warning(FutureWarning, - check_stacklevel=False): + check_stacklevel=False, + raise_on_extra_warnings=False): with ignore_xlrd_time_clock_warning(): df2 = pd.read_excel("test1" + read_ext, "Sheet2", skiprows=[1], index_col=0, usecols=3) @@ -826,7 +828,8 @@ def test_excel_table_sheet_by_index(self, read_ext, df_ref): df3 = pd.read_excel(excel, 0, index_col=0, skipfooter=1) tm.assert_frame_equal(df3, df1.iloc[:-1]) - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False, + raise_on_extra_warnings=False): with pd.ExcelFile('test1' + read_ext) as excel: df4 = pd.read_excel(excel, 0, index_col=0, skip_footer=1) From 7396ad6dcb768d34a962603aa13b77e8f1c42123 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Mon, 1 Jul 2019 20:45:05 -0400 Subject: [PATCH 48/55] Updated to config_init.py --- pandas/core/config_init.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pandas/core/config_init.py b/pandas/core/config_init.py index 84ca154d045fe..7fe9f8438ac74 100644 --- a/pandas/core/config_init.py +++ b/pandas/core/config_init.py @@ -422,6 +422,7 @@ def use_inf_as_na_cb(key): _xls_options = ['xlrd'] _xlsm_options = ['xlrd', 'openpyxl'] _xlsx_options = ['xlrd', 'openpyxl'] +_ods_options = ['odf'] with cf.config_prefix("io.excel.xls"): @@ -447,6 +448,14 @@ def use_inf_as_na_cb(key): validator=str) +with cf.config_prefix("io.excel.ods"): + cf.register_option("reader", "auto", + reader_engine_doc.format( + ext='ods', + others=', '.join(_ods_options)), + validator=str) + + # Set up the io.excel specific writer configuration. writer_engine_doc = """ : string From 5a440a42982f9b9d8a871b1c611ce99f7c065060 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Mon, 1 Jul 2019 20:45:41 -0400 Subject: [PATCH 49/55] Updated whatsnew --- doc/source/whatsnew/v0.25.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 8f3a865ec8bfe..a129f5b3573a2 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -164,7 +164,7 @@ Other enhancements - Added new option ``plotting.backend`` to be able to select a plotting backend different than the existing ``matplotlib`` one. Use ``pandas.set_option('plotting.backend', '')`` where `` Date: Mon, 1 Jul 2019 21:18:52 -0400 Subject: [PATCH 50/55] Updated io.rst --- doc/source/user_guide/io.rst | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/doc/source/user_guide/io.rst b/doc/source/user_guide/io.rst index 9af6c36cc4e4d..c10c4ddd819b3 100644 --- a/doc/source/user_guide/io.rst +++ b/doc/source/user_guide/io.rst @@ -32,6 +32,7 @@ The pandas I/O API is a set of top level ``reader`` functions accessed like text;`HTML `__;:ref:`read_html`;:ref:`to_html` text; Local clipboard;:ref:`read_clipboard`;:ref:`to_clipboard` binary;`MS Excel `__;:ref:`read_excel`;:ref:`to_excel` + binary;`OpenDocument `__;:ref:`read_excel`; binary;`HDF5 Format `__;:ref:`read_hdf`;:ref:`to_hdf` binary;`Feather Format `__;:ref:`read_feather`;:ref:`to_feather` binary;`Parquet Format `__;:ref:`read_parquet`;:ref:`to_parquet` @@ -2779,9 +2780,10 @@ parse HTML tables in the top-level pandas io function ``read_html``. Excel files ----------- -The :func:`~pandas.read_excel` method can read Excel 2003 (``.xls``) and -Excel 2007+ (``.xlsx``) files using the ``xlrd`` Python -module. The :meth:`~DataFrame.to_excel` instance method is used for +The :func:`~pandas.read_excel` method can read Excel 2003 (``.xls``) +files using the ``xlrd`` Python module. Excel 2007+ (``.xlsx``) files +can be read using either ``xlrd`` or ``openpyxl``. +The :meth:`~DataFrame.to_excel` instance method is used for saving a ``DataFrame`` to Excel. Generally the semantics are similar to working with :ref:`csv` data. See the :ref:`cookbook` for some advanced strategies. @@ -3217,7 +3219,20 @@ The look and feel of Excel worksheets created from pandas can be modified using * ``float_format`` : Format string for floating point numbers (default ``None``). * ``freeze_panes`` : A tuple of two integers representing the bottommost row and rightmost column to freeze. Each of these parameters is one-based, so (1, 1) will freeze the first row and first column (default ``None``). +.. _io.ods: +OpenDocument Spreadsheets +------------------------- + +The :func:`~pandas.read_excel` method can also read OpenDocument spreadsheets +using the ``odfpy`` module. The semantics and features for reading +OpenDocument spreadsheets match what can be done for `Excel files`_ using +``engine='odf'``. + +.. note:: + + Currently pandas only supports *reading* OpenDocument spreadsheets. Writing + is not implemented. .. _io.clipboard: From 93adedb98252643aa7c0b36b90bdd280f52ec2b0 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Tue, 2 Jul 2019 00:04:06 -0400 Subject: [PATCH 51/55] Refactored to simplify --- pandas/io/excel/_odfreader.py | 72 ++++++++++++++--------------------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 70cd5933a9abb..eb20b90c0c794 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -29,6 +29,11 @@ def load_workbook(self, filepath_or_buffer: FilePathOrBuffer): from odf.opendocument import load return load(filepath_or_buffer) + @property + def empty_value(self) -> str: + """Property for compat with other readers.""" + return '' + @property def sheet_names(self) -> List[str]: """Return a list of sheet names present in the document""" @@ -56,50 +61,40 @@ def get_sheet_by_name(self, name: str): def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: """Parse an ODF Table into a list of lists """ - from odf.table import TableCell, TableRow + from odf.table import CoveredTableCell, TableCell, TableRow + + covered_cell_name = CoveredTableCell().qname + table_cell_name = TableCell().qname + cell_names = { covered_cell_name, table_cell_name } sheet_rows = sheet.getElementsByType(TableRow) - table = [] # type: List[List[Scalar]] empty_rows = 0 max_row_len = 0 - row_spans = {} # type: Dict[int, int] + + table = [] # type: List[List[Scalar]] for i, sheet_row in enumerate(sheet_rows): - sheet_cells = sheet_row.getElementsByType(TableCell) + sheet_cells = [x for x in sheet_row.childNodes + if x.qname in cell_names] empty_cells = 0 table_row = [] # type: List[Scalar] for j, sheet_cell in enumerate(sheet_cells): - # Handle vertically merged cells; only works with first column - if row_spans.get(j, 0) > 1: - table_row.append('') - row_spans[j] = row_spans[j] - 1 + if sheet_cell.qname == covered_cell_name: + value = self.empty_value + else: + value = self._get_cell_value(sheet_cell, convert_float) - value = self._get_cell_value(sheet_cell, convert_float) column_repeat = self._get_column_repeat(sheet_cell) - column_span = self._get_column_span(sheet_cell) - row_span = self._get_row_span(sheet_cell) - - if row_span > 1: - if j > 0: - raise NotImplementedError( - "The odf reader only supports vertical cell" - "merging in the initial column") - else: - row_spans[j] = row_span - - if len(sheet_cell.childNodes) == 0: + + # Queue up empty values, writing only if content succeeds them + if value == self.empty_value: empty_cells += column_repeat else: - if empty_cells > 0: - table_row.extend([''] * empty_cells) - empty_cells = 0 + table_row.extend([self.empty_value] * empty_cells) + empty_cells = 0 table_row.extend([value] * column_repeat) - # horizontally merged cells should only show first value - if column_span > 1: - table_row.extend([''] * (column_span - 1)) - if max_row_len < len(table_row): max_row_len = len(table_row) @@ -107,17 +102,16 @@ def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: if self._is_empty_row(sheet_row): empty_rows += row_repeat else: - if empty_rows > 0: - # add blank rows to our table - table.extend([['']] * empty_rows) - empty_rows = 0 + # add blank rows to our table + table.extend([[self.empty_value]] * empty_rows) + empty_rows = 0 for _ in range(row_repeat): table.append(table_row) # Make our table square for row in table: if len(row) < max_row_len: - row.extend([''] * (max_row_len - len(row))) + row.extend([self.empty_value] * (max_row_len - len(row))) return table @@ -135,16 +129,6 @@ def _get_column_repeat(self, cell) -> int: return int(cell.attributes.get( (TABLENS, 'number-columns-repeated'), 1)) - def _get_row_span(self, cell) -> int: - """For handling cells merged vertically.""" - from odf.namespaces import TABLENS - return int(cell.attributes.get((TABLENS, 'number-rows-spanned'), 1)) - - def _get_column_span(self, cell) -> int: - """For handling cells merged horizontally.""" - from odf.namespaces import TABLENS - return int(cell.attributes.get((TABLENS, 'number-columns-spanned'), 1)) - def _is_empty_row(self, row) -> bool: """Helper function to find empty rows """ @@ -162,7 +146,7 @@ def _get_cell_value(self, cell, convert_float: bool) -> Scalar: return True return False if cell_type is None: - return '' # compat with xlrd + return self.empty_value elif cell_type == 'float': # GH5394 cell_value = float(cell.attributes.get((OFFICENS, 'value'))) From 62a37e73bb252802c79908cfa7c99c56c32a73bc Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Tue, 2 Jul 2019 00:04:47 -0400 Subject: [PATCH 52/55] Removed unnecessary test --- pandas/tests/io/data/raising_repeats.ods | Bin 2929 -> 0 bytes pandas/tests/io/excel/test_odf.py | 6 ------ 2 files changed, 6 deletions(-) delete mode 100644 pandas/tests/io/data/raising_repeats.ods diff --git a/pandas/tests/io/data/raising_repeats.ods b/pandas/tests/io/data/raising_repeats.ods deleted file mode 100644 index 926caa99d070c1f4679a58651711f5d47426319e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2929 zcmZ{m2{e>#8^^~sm}H$SEy|jGiA46DCX6LZBN|Nh!OYl35we5`QFxOr>kw*`L2t&+ zkbNB_B70&iN#4s-J4`a>af^dP|BLjVxvr4lDYeL0#; z)amW+jlcx^BH+HhUhd9tjJpp~HUQ}&;{!*#qh)-25l9ywXMb-55+j55MIqoWXg34` z1Et0U1Aspx2mmselK7Bx0Du{Fo1>l3m|!miS|-TbtJ%5(IU&d1!1Gy?i3R&eL`6J> z$sFa@=WMph*b5d{$TbQta4K?~o+%W8_{VmZ2O(Jdo-n;0-@Yb>ex!F&7m;K_ty+wf*hmc0OvWJ=%$D++~wh!m~+|^Fg zymUGBr>wkRTfr^+SRkKBP<*vo4`VRu|47!dcy)s_uUuYSzIMvb1WjQ!h;G*1<=ZuK( z(RDF;#20W{-7>yme$p@9FthFwR}Jg%_5BeWVq{ja`dy~egEPsTo)=KfCpA8DInCct zu^TIVwEtN4UmnJ`hRx z*K={WsXJLps3!Qh6Im%&ekPDa--@9HUa{?1RJ{VNAu6 zWZj6G6=QGk8Tt_k3Ct7gxayR)dis`UV#wGSn^tdiiMAF^+{&NNfcK)Vk;iXIK_MI5w++_wsVc+*xC4rPnRb+n2jBz9D&dYkktCklrL#4d{Gr*xHSC z37Bf?lT|F60`Q2juFqxCX{kWklRE7l{bIsoGPt1Bhdl&+twgm9pi7F9#Z zr#d|{Bzm_Zw=i{sG05cf7^jO@Tnaa1oF0pi^H9bRK6citgh|od>~!-(vHQgzyf>n* z^x1}+Y}C9kOf_vE2reRfcgj!A35M{GxUc)iAe@B>**dL`JTtsjxraL6(+icW@Jw{# zn~Awfn45}1xqUS)j=lX3-@G_H{HhLj?u09+&ce)-Uml+KV8!-C@Wv}NVrh^G_YpeC zV&QfQe^I}l4G_@?cxup7<5q(4HD%h%34wIO*9b%YZj_Z&0q2`d8K_nrgTP4619O2_ zE2>c*SCFmsA3t8(ZKXXhJ?VyT0Yw#{G^8@vBKCE8iSLKL1>d-18ulIsV;u;kA6S|J z30072M0epE0H?5bdvO}=}^^;f`xJ$X!^`s zBuxQVoc(wbebOgph6d%BNHt8yaZ7IcEVY7VgrdLjMU^k-n%f>1d9@6<#Ov87ypofU z?E^laV&cQjfzGfFEGFhuGk!D!md)0;jDKpk+5IE;ihhs{kasBAo#*^wNCfY_{qq{? zq>3S|W#aS(BuM4?jwNX$P-h&v?`gyX-WE{$pyoNZeqr%YMra_&6>z@Kq`=#PXjaoR z3<2*#v<%Dj#7XyVA8vhJ3{Lr4kI-QML`(Y&$)(KGzo(CqFaMeY*_1l$OhS7#@8(S( zP`Ujl*)g4L8>7?}Ff9OZf_gX6x6+n2HZ_p-h9li?A<#b=$34{cpp$3O--()P4AKmE zDJh!0f{oY)5Qi0kFeM?AcCv;kJSD)~C|A{(d;LyunDS$ssWQ5Bj?z=_76taWy0u7Y z+n@MSdg2)Lo;`DeLox+^J9L~UM^PYt0yJdY{Cap9A!-xOr5X{jBK&6jq0@1QHs=m{ z`o`q!8P(?rx{g^kcs}y0IW@(H`6JfTZ4Ay4+QY+}+MX&54?`2x%@rMKNP#bAl!Wv& z1mkLwPT377cwv_u2P0goMiw-+bVU{oe<-AqYxnHQHR;NpmXw-!d8~p%q39G-eU={R7_{yn1U4>%#(rtv zzg^G9_Gno*KULP(!Ol`3ljG4{4r_^*WxZYV@(ud;*~mr}<>8<+jrsm}?pzyRwPwEA zA6G-e)eodK!z#>B?uSIG-=;$-La3i-t2w-<8t~li)umr`?4ehG{OpMUqsZl z-za=#x|WE_b0j_ouUw*dDH9B?JUf@Brqyunl~IR8cSrP8}Y7Ee_A?f@&W%ek4ImBjsI9lDC9`>@AmMQ!ixI% z%Pjt``@Mnv(k)T{@4wpF@50~t_$6#+{1;k&5Al0l{R;7(T4q$s{IklS5C-7U3Ip{z LN3BBDpFaC96Lk^i diff --git a/pandas/tests/io/excel/test_odf.py b/pandas/tests/io/excel/test_odf.py index 3c04cd6e04717..76b3fe19a0771 100644 --- a/pandas/tests/io/excel/test_odf.py +++ b/pandas/tests/io/excel/test_odf.py @@ -37,9 +37,3 @@ def test_read_writer_table(): result = pd.read_excel("writertable.odt", 'Table1', index_col=0) tm.assert_frame_equal(result, expected) - - -def test_raises_repeated_rows_not_in_col_0(): - with pytest.raises(NotImplementedError, - match="merging in the initial column"): - pd.read_excel("raising_repeats.ods") From 13fb76f340de86519d83745b49600288e64ffeae Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Tue, 2 Jul 2019 00:05:23 -0400 Subject: [PATCH 53/55] lint fixup --- pandas/io/excel/_odfreader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index eb20b90c0c794..7b1bd5bed29fa 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -1,4 +1,4 @@ -from typing import Dict, List +from typing import List from pandas.compat._optional import import_optional_dependency @@ -65,7 +65,7 @@ def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: covered_cell_name = CoveredTableCell().qname table_cell_name = TableCell().qname - cell_names = { covered_cell_name, table_cell_name } + cell_names = {covered_cell_name, table_cell_name} sheet_rows = sheet.getElementsByType(TableRow) empty_rows = 0 From fb6c5eece0e9c3fb89468b725b95a73df328ee30 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Tue, 2 Jul 2019 07:51:45 -0400 Subject: [PATCH 54/55] mypy error --- pandas/io/excel/_odfreader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 7b1bd5bed29fa..c820c1497c3c9 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -80,10 +80,10 @@ def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: table_row = [] # type: List[Scalar] for j, sheet_cell in enumerate(sheet_cells): - if sheet_cell.qname == covered_cell_name: - value = self.empty_value - else: + if sheet_cell.qname == table_cell_name: value = self._get_cell_value(sheet_cell, convert_float) + else: + value = self.empty_value column_repeat = self._get_column_repeat(sheet_cell) From 4026fc1f5ec58c9482339293928cda0f92143c94 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Tue, 2 Jul 2019 08:41:59 -0400 Subject: [PATCH 55/55] Doc updates --- doc/source/user_guide/io.rst | 7 +++++++ doc/source/whatsnew/v0.25.0.rst | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/source/user_guide/io.rst b/doc/source/user_guide/io.rst index c10c4ddd819b3..bf7ec561b4a7e 100644 --- a/doc/source/user_guide/io.rst +++ b/doc/source/user_guide/io.rst @@ -3224,11 +3224,18 @@ The look and feel of Excel worksheets created from pandas can be modified using OpenDocument Spreadsheets ------------------------- +.. versionadded:: 0.25 + The :func:`~pandas.read_excel` method can also read OpenDocument spreadsheets using the ``odfpy`` module. The semantics and features for reading OpenDocument spreadsheets match what can be done for `Excel files`_ using ``engine='odf'``. +.. code-block:: python + + # Returns a DataFrame + pd.read_excel('path_to_file.ods', engine='odf') + .. note:: Currently pandas only supports *reading* OpenDocument spreadsheets. Writing diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index ac133e37ef260..35e9fe5706b31 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -164,7 +164,7 @@ Other enhancements - Added new option ``plotting.backend`` to be able to select a plotting backend different than the existing ``matplotlib`` one. Use ``pandas.set_option('plotting.backend', '')`` where ``` for more details (:issue:`9070`) .. _whatsnew_0250.api_breaking: