From 6658c827c13ca38a91bdf3e23647f84a4d401f1e Mon Sep 17 00:00:00 2001 From: Addison Schiller Date: Tue, 10 Oct 2017 13:53:58 -0400 Subject: [PATCH 1/3] Xlsx duplicate header error fix The tabular renderer will no longer overwrite the values for headers that have the same name. Instead it will rename all duplicated headers in the format `name (1)` --- mfr/extensions/tabular/libs/xlrd_tools.py | 34 +++++++++++++++--- .../tabular/files/test_duplicate.xlsx | Bin 0 -> 29070 bytes .../tabular/files/test_duplicate_uuid.xlsx | Bin 0 -> 29698 bytes tests/extensions/tabular/test_xlsx_tools.py | 34 +++++++++++++++++- 4 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 tests/extensions/tabular/files/test_duplicate.xlsx create mode 100644 tests/extensions/tabular/files/test_duplicate_uuid.xlsx diff --git a/mfr/extensions/tabular/libs/xlrd_tools.py b/mfr/extensions/tabular/libs/xlrd_tools.py index fe4b19742..ca0c19fe0 100644 --- a/mfr/extensions/tabular/libs/xlrd_tools.py +++ b/mfr/extensions/tabular/libs/xlrd_tools.py @@ -1,13 +1,15 @@ -import xlrd +import uuid from collections import OrderedDict -from ..exceptions import TableTooBigError -from ..utilities import header_population +import xlrd + from mfr.extensions.tabular.compat import range, basestring +from mfr.extensions.tabular.utilities import header_population +from mfr.extensions.tabular.exceptions import TableTooBigError def xlsx_xlrd(fp): - """Read and convert a xlsx file to JSON format using the xlrd library + """Read and convert a xlsx file to JSON format using the xlrd library. :param fp: File pointer object :return: tuple of table headers and data """ @@ -34,6 +36,30 @@ def xlsx_xlrd(fp): for index, value in enumerate(fields) ] + # Duplicate header fields create errors, we need to rename them + duplicate_fields = set([x for x in fields if fields.count(x) > 1]) + if len(duplicate_fields): + counts = {} + for name in duplicate_fields: + counts[name] = 1 + + for x in range(len(fields)): + if fields[x] in duplicate_fields: + name = fields[x] + increased_name = name + ' ({})'.format(counts[name]) + # this triggers if you try to rename a header, and that new name + # already exists in fields. it will then increment to look for the + # next available name. + iteration = 0 + while increased_name in fields: + iteration += 1 + if iteration > 5000: + increased_name = name + ' ({})'.format(uuid.uuid4()) + else: + counts[name] += 1 + increased_name = name + ' ({})'.format(counts[name]) + + fields[x] = increased_name data = [] for i in range(1, sheet.nrows): row = [] diff --git a/tests/extensions/tabular/files/test_duplicate.xlsx b/tests/extensions/tabular/files/test_duplicate.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..a8151ab4d5f9cfc416d6e753eb3a656f4f157ccf GIT binary patch literal 29070 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 literal 0 HcmV?d00001 diff --git a/tests/extensions/tabular/files/test_duplicate_uuid.xlsx b/tests/extensions/tabular/files/test_duplicate_uuid.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..f10abfe1e54eb8716c9c3dd4919421ed8a49f227 GIT binary patch literal 29698 zcmeEtcU)83vhc0L^KN|!_tB824Ica87R$a;dH-yK%+w4{2IIwR;$AxV{^aY1xPTLFt#{=b1eJ;du1UZ> zwxaHPjK#B^*I>yOt!FSX+GS$edFYfzOZ@H|-X-fJHhH(tD#22U#pc?bjH$(>hA8pd z?`!sEU%Y?f*%@7VjE(+w)S0tE%G^ ze!|`SzDV3Nj15<-)csyP7v1~}6&2*jm@{s;yy|pL*ri@298R+foV>`unyxVH#PeXi z;L=N&fJs3%;hsVB?kzl9hMDdmoft~y4bKdoiy!=7syeiA=zji?27EnEn_E^3A&m>- zirVi%mu{9Ryj*E~CX}h5y(3O7a*W7(quqNiq~xaVEWT*wK{SW>D?voTT+s?QXkQcx z1hDxlCyjY~wtxwA(pk_489*m>3-t;JQ&c#(|9|KFKkSiz68-5d%LC_3+%fymksGsE zCw$c=KZw;fiY^gydc*G`JLOfRNSkhwv0(}Ox%g7u#ih-6dEdR2bfe2z>uV2X8`EEZ z8h&cV)0dRfqVV0ET0 zOtt)?^vT1w$#lxy5Opp)N{2KW=l}3^!zumAC;Y#BA6hD;JbcF_arW*F3vOSmp(KO3 zkK)tbK^yj>`>6L5gqGr0qb~;_v#!h^x1;4(hF&L#eLcN3;NPXh*t?m~mM|0( zGMuh#lJDAEG$9`ic*utMUgWpf%W5oqU1a$nJm&&q;Z@A0)Ps#LK1aK`C07$1Y>r6K zZAnhfMIF}5ooJC_?VfhlrOrGoDLy&Ys6L~9RBg%nEZ1!<5i?4RZ7h>$ z*6^s0(vjJ;4cFv^&d0e+k5}qe)Es3PN)~xtd+h-wrcSq!Kk}Xer!nE?=FO?V>La@% z_jATSB44B^vnS2i=2~Q6(AviQ*HOQedA{Mhx#)u>1QN(DNsYUD zk!pIUK0rsI=F~BQlJd!?e(g$3F*}FFdQckKDk&;`GEEH}Xx*lUACsqO&hXjgUbJnk zX$^Zd(V(5T_MV5o_p&DK^Yi@cbN)r2_wzqKNbF~g`(Tg{mC>Tmsy01B-!7{YIQt-v zN4UU;B5D3|qj$Mj&XCD%n7FZ#bt6aX7@KTvkj{kM<~^3lb?;2V zp24of+puRoMf6dPcY;HfLN!I-`rVgdz7JK6J$+GwCj^dMJ8FhWeuXG_$Wj<1t@7+q zgi0R_RuUd@nx!J8to&S^MP^9dg=*hAD=Rh2?c0(vG540L_RzG|5>ewb$K;yEJaaYI z4@*5pO2 zY=|PG<9n|pM3G{%y<%##V@o?wxNc=T$oGeb5=K8O43}QX+!^U-X0#|$V7&M23c^!k zw-4GUSGLZsK6vdq6#imEna1@a+vC&kFP$ zU`A@mZll>OKZO3F?JKdQz}cVxFeh+O5&X3Ma9^)LFU6mC2f`Cah z!b2*O&otu6X&)ns|*)mFCIHs$6xKQ~Y*Y93Rwe zeEo<{8sUx2g4iH`?VYhxUNI8AYVRn@|~(Y9wA%{pkOw zas}J*St=siq3+HAyC3Z4GU!3rw$YYhgquc+0G76rR?T06u)V z(|h?{55G;fZ>Uo8IMqg^>IRKx_Y{*N3bMd|hf&Ya-~{JXX*mzUNd8Sd4{YHf1Hj~Y zeu(<9-zgJ~t8zh$NMY%5js?qj3&gsO^itB+1+%31vM0Q)g%=;C+-r9jz{Gqf$z1+U zdi}b~M~VflacOJ$BX-9=M+^y(TC7>7!WU7rW8UQ48}z=KDsu^s`0E-CnETcu#Hw*G99{Yka%ZX2|R!&PnYpxVU#!Q_Z!H zBhjPqjb4W1J-qXIV?D8GJGSf)fyctb9-SJijH$Qv?r9$)$f&hf#*p|ikNGUuitGen_kaCdbY^NT8sSUC;J&&*%75@-)}Y-I#1^n7Y)*pgt$|=Df8f z?+b}{bIzTI)YdzTvQ?9##i)5WfyN}@md;ruUR3DgckHb=gT-tTO0C4H|WXOTl=P`GAEWoi&}%3XcHN$_xDVaQ>FtjmfiQ#@7IPD%uf7dT!kGxHHt zCpn%=?7v1svv@n?SjlvUN{zBwb&bt)qXEwix5|m|8kg&k0`HppN8tQ7L@9TVu==al zd8&y%I0kJ|d-$R@(^2Xphp~|&pWp+kyp$M5EYzwVW_S$S*~wWK8#iHTI}74ceV>HS`cs>4<&piT!j z6u82w5Jh9sY@18PeGE64IGmE?LbcYuB5*@K*pQ+63aaVi2?|5#BTD0OpNP9PNeM;M zZ?C;u@$4D)gD-5(HBDZmEw|c`2q%9gaZ7Sl>;-(U{c57)Cf1*^ z%w6kZD_?!shX7Zrc|13G)r)Ik*a@yZe!s8c`v;8_Tx*}w`E?)LCg{msq4ShSbocgM zA8?QA%_q;LEqqJ(#A0CYHtfQrVy14k2M6{9wSBY9D{4l)wT0d}wV!vxo7CUk;hdVT z@xsMe)m*c@t(|XojoUo3_gPoHfV0_gqwVn2&OvEd^4*trSzJCL&YlcAqPZ!w?EF_BY3EKP z%xf-dpyu{e0uB!OPJ8?D;)GqcGR?<#Qi3QfCr^f}?g-j_==r@M+sL(p-0<(SM;l?i zhXr8f_=O$-nEsMLgkK8q`k6Vb+glvuj_p5lN5maZzX|GwYk~UD1noI}Y1fa<)QB0e z7~UE|Vq$NvPYTv-T{+Ul+$PRx9E-BcsM@hD5>U;CA6Z=aK`mCMk{@=im$-L1A~Nx2 z#Mw`0eS0o`P`Xx~wX-R@#@F#h{kkSK>ziKW;)QzgZf}0c$GQ_o?1Jrl#U3XV%cNE^ z7Qgs@%J#;TXQ;XOr^AhO7WZ_aMz*z;6}6}P)3gdgJ6t%PDczd-T6T7N@N8Vg-pYM% z=z6e5e2$Ll^lE252eVZYBcqMz0c=zv+H^Ok=`sFe{+yPXiqtS}Tk1KIS`g!oc7Q{mSs5 zw^j>}%#B}^Qf&i6c~T8aW6n6+v(wvaylTRJ>ZNGAbReGOCy>n_-XF1*Jr*YpP0xr6 zR*~#(6Uq}Z&fI5vwnJlEf5AK|tB;3vFlR^qq_Dsgt{(alR;os`50e#fI`{BJrPrIc z$4+}a<7(2&=~@X(L2IDdThHG5Q3c(J@5~>{@KOj!AG}yjy0`B`Zv6!HCyv9cw^ezq z;o2?5&%euJ&-U%n>nUH^dssLh$C6T9kDsw!ekVq;?M9X+4zkFZy19?>c8qb&6#`I> z?C}d4RXW5&#Y_WXpWVXTmX;RZy3sFvHP$f2zKqN0yfTn!+NEim?DGRLs_0r0yEgGi zgY(c~n2JDIz_O@xc)QScY*Pf;P@LF&k8mh7%sts#wjzn`3fabNS{|D0%@=Sx6>1fFD>~?l#EZ%2 zoinT=GK$dHQRbM;+jo8HE-;haU{aM~^I3=Q@E${&(~|jbl8txwoohbf&Q;N!5tP0i zx=?-l$*zGgAx%kqrd3ozNt2sV=5Csou&jZN_lf7CDP7Mvl#DFqk8Slhs38mNPWK+I z=9Rf~gsnvX@E1jgx?9lWNf^<-8%P%i`L~WV>P_ox3I%Qwj@;Yqbc7Rvlx=bBX zl%^xi^M>U7$hM*edW@kQNGy_mcc?Pu*+YI6QDbGi&@c6*!1r+?M_G?rKgwh&w>;e` znAoU)>pGHfQnfx_4D0SGZn}1Ie_@9IWR5J&GVl3^`71sM4^ib3BezeiJv=hECF2pg zuPWP361OZ;cb_R*J~L6IO44QjoTqj=p6LVq<_q`yYq=kmVIP{__q>a7i1pqryK=r*=#BD`p=5O` zas1sL;=JQ^OY;4ZB(8`IUdJuN`PPKz#sWUoa;qONUOdyc0>7puIDdXLB+CCx--Pou zt)w|ewb1yy4R@*LAA*scE%RMgC>z4=tf>%{Q*B`C^w-COx8B}7KWuxM=5@rC`WBn0 z&g`OmY1ey)or6m?RJ)FBIUPE})!-iT{)N`*BCXd}?@ClKb54U-D07-jT}{#K^$OZu z&mziTA?5rH&9fSOk*4m|97$;A{mhN_aS~4_)s*|i-2E4U+kTyq`+ZZj>Px&^=X_;Z zBraX$c}M(2FE$%w$#SJF*mVDR4pRnNbH(jX5&SR+A6z1U)=RgkyS4yu@b6$t))V1j z6N(5918bm&z>7idegO*p!CpR;Ny-AiddkGy1fT+oMKkaZpkRS;W2B!q09aZA$G~^# z0BR~e0IW2B?knk0@&68+P)P%jpY5QJCHn#35zJ2XkV9yX(Z0 zu@oG1+1$|3&BoT+#Qe1JPeGuQ?!mzUhlBwjFep6K*7T(Cc}FK<=oEMc#0zdf^8pO* z9$~=-Ha4dZXddAHr2SX7?eU+y10(YPxc{$^|Mr2w(<{sa6#n3h3gH>%5eULa0HC?= z5gZy00MLW7Tq7zx_y9iOfio0j5QKj@fPH?2!9IZV;;(SZPn@&1h9J&;5N7u9b@v3} zrywkM@lWkOe}aGI0mK05n0f_yh5C62ALs(K1aH3pub&;i-u_AVpD_PBFfbqjl=KsXJKusGlWqzP#Vi?lmvKj3Hb zvajI*p9k%4{oPO5fG{5je|eYshPR)I`CssBd_&C++7I5H4i5NfqabfIhe9K44`c_&qET=U zH8urdkS2|dSJ1fweICG(fIXEv-~}Lni{RY@2m~{)(rIj0e5z>G3 z#D}{1c?@LY|D-EF@J;yd81jFmQR{!EbDEDd%QR!)eTwG8?=*yIW@tWue^a1t{*VRn zNB>{6d@xgf;EW>v3h!^{;x{Y(Zw7)|@B?M``jgD^f2PrgYUTN3v@z*{Eq+M+0gHE4;*lQU{?oxA6VCK68>4&pv5wuWaeiUV>V(IVwPY&{X3R0GdHs` zvl+7#*u($(y!~O{!he<=)QclH+Hd;#$73D%ir?>)z&W5|0xiJrj}iaM2F`&T`vFlP z%}`L^2lf^OTmo_b?0J8Yh+2?ZncDnstXt#2=6|D2YfNiIYXAt-9;4NyRi-_40RQI8 zv{GQ7CU`ggGjE`M{EqQgl6r-EArICQzz`99Db&x$H(XdnNl9JU09^XKgw2CI6y${6 z0|JD9c7+LtdWCs~MtXTF00-;l!FvGk&GzSdNX0eqM_afh0O)=JW8dH(ZB7vY@H7le zJJkMYlLlkukwO4yI^hu!8u=?daG?e_sl&WgRenBGF`fkD*BpiN4UA*ZTL7?slS0{h zNTKW(gY-WDz{>#2OMs0I$e{utRBXT@HYx}k6{Q;hml`S>s-M?i2m@4yAk;LpP&)d< z3?M>1D{zPk0y#ttp`kfQD5zqdhFwJ2fR^KoJ5)4;Q{~Fd2Xta5pLZOw9m0#N zUJQ+=Kg`9=!^^1?3}yzavv6#l$Mo0di3#E#gCspFDOgQdY8nw`T6TjoQ1=iHQI#unoF{KSc-}!TrfQ2naxrx1FqgO*R)TOK+E2;;XM)B5 zTax`Q*dKDufQ!LzLjAkH6p+Zj`|j`WQD(seRG2aiFhZz6V}h^&Fo0aD1ET1xtHcBLlf`{-|t6)NMuas#lJ)dD$03t#!QXFElv2PS4F(Z|$rKO?=;ny*7P3 z93BI8!6cj>BJr2{JM1Vn@H<;d&N=e8XuBWk_!229sv{}Q5Ro9 zPMt`A;q!+n0I>&%^^P6Ea8Lm3x!55zb2tT{B?%8J#vVeckC9m1_iWa<3hB3Ed`yz& z7p)$^lImoFNG{@{d!4l?$utBb$!OetsB#n5P^ZY*MO!M-)|Mw57kiHabQTcLQ2?X0 zjzXf<3Yv5=j)iD9e;Bizk5r`q^s|Z-;JOVod!D3B3@_v($rP7gA<5;Qo9LWR(aL{+ zzge;PQvxUd#!T7o^ z$uD(R%(MIJL2Hm3tyB>Yj4R;HL8ZP_yYDE#;b^SGK1Vez8mnDO0SIXT3~P-#Mgbm1 zEDVsO!ZBoJeZezW>m)bkFYU)j7e*9NY}iX%8^;E)BP$Im%em3hlP4aOtg+7@FJYq3 zpl!LuQ_`eNqpIUOaO+{5t{a(ir?Yxj%9RmRWRwL3SmdN)8t7C^BsdR<3o+^NhUf&p z!FPc6NxmagcuY(!_(yusIh{T#^_z*aS@&(AOMUAyQ47}Tf_%d$O9FpiJ0n4=w-Vmv zH2#2~y6rDWO?1GfkfF%<{vNhTe?cL)!&AtUeYmm=GGkspK5HJdyXm*ClBxo+$I%Vv z`vK@Of+Vw<=(c6m%1Z&Re#xN#z0&{4nl4#)!a$+FyD0XC`n%HM>q*(@BkfWvk=0QT z{dQOo<$mqTqZ{FSJWl8D_QAd2Mc|<^Rj!7w{QP^NS zS_ilG@>*tQFf4xGW?g14STI=V_H=*w`cCfpfOy9H?pv0?Ww*bd49LgAnOf_iSO)Uz zLf&>MlnNdZU*~p|Xl*o9wIy(lB!G>vl#^&9Adu*tNX z`{f$Gyicp(=2O4R#}l)Q%luEo3bq#v@AiI~6EB$lGVN*pOM#TR*iA!O8DJ1f86>x& zj_iqd;R@-HKHbT<`HZdFs^!bxPs3;ARXb{&&hxKnR56v-frtE~aN zZhpx2Uy7jsfKFCdVeNcktQ_eau@7%g0s7{T*6F$}oW(Flz-O(#xFzofCZ{YA6Wwvz z6oAElm!|Kkx<4FF9m|hlpMiH-x0t)qTiw4Z_W1oC8Hg52f_cCgP?E&N1w=~2JcphL z+-W@L(Q0!<{!*%<+P?Fi%J8?&cJb_OizrNO8*))upB&gs0gB(e&Fbg34h$HQ#Sr0& z{ktUnLWBJ#3gETLL3FbwR3+|NTRcKqpCKTUP)E2h+*iFVRYuMe%JPcff~+lLRbF@% z$y6+wW2HcPP%)f^Z;iIEldq1ivSM;AYjeiz@MZOll|6~IIjNjZE}{Ceg50;ZJ#T%ewJ>n4i6X%(TaQJ?Lt zK=e+>Kq{GTzeq;{mQH3Pd0`02NgLmL8Z2Q2@i< z7hs^CXGDL^jni?%xfWyR6Pxn~dNNZAITdvju>%m4GNFFE1=nUf(?xJx5fmcN;9bUc zb(EKf8SVvrdAhY#AJ6ZcZn8>{Usqe6iyjaSW??2e>;;n!6CYqL!+MGrt!fwgxFO_iCL;Em^JoA;i-zNn*!0ypYTb!QS1hYq35`?88c>|1uOl>pDH)_&iW@b;POW zSxaN~abSD?3Wl$pk38%uFiLX3+b!nLXN2qWpQ#!Cu3aI|a(d)*UDFr}R!pol$+cke z>Eer^0NarmESlA~KpemQq~To|e44Amqr!J5s)1Yif)cbIjPu%6OHm}r-MMuS*i-?} zdtKH2^!w2^hrk|KHF}UxgO8o*0MjcDC`dvC4pYjc^yPH|M{zu&ik%C+irLtZD_7V`t>;%*N|jSXZ>l zlB;~DEAJFME?(n{_>%X?$$8G{9Xk*ui>>q$G`H)`?R>2;aKd=FRZh@NKC=Aoq7maB z_b1DqhH40aixUUeDzqyFc(8bZ0(2mfU_UJNp(L^0oqZO*B{Bm_nF74=2Z->+s0~?s z%eD$$u)do3t#3{`(5+ifiJ1BniFnz}oK@`aDnW=C)jKg9zRBuu{{p|?Q*9 z;mt|qf)AnzZq{{L-dPWj>b7GMwAo7wh@NM*?|UzaCBMA9|Mpe(c!esUjpN=h#BS28 z+BhPk9f=79?`iZ@W3N#C(I_{@Ixv>4As(SL@!J&(8?s#q`Was)te%Zm$BMwxzHI6! zhep7exO7!Jcjt_;VUmnO%qYu)fMbYm+Xw_9{^jP?1 zce4wVncc3eCv+!ZU2Uz{y~3BT`bzyQf49A@wT`k?_+FkxMIQWPIIwH?_jY)k1gly{ z5f?9r!9CI5M30*p=+8_m`a*|@OWPQt$evPThfo6f7_q#12EO|_8OtEsCT2an6h7o(^EA*2DKDg;+?Egn;ebn7IdF&ZtoDQ!>AL*)>1Lr_A4h6}TLg zN&x~Cr}{eDB}a6GaIopi6IKM~ifU(K5I@(joC5oZ?TZ9#dN<kM;R{2hv72X0|HQiPmsnDjwqo83&dgnu$5+)Pvkb7rCndU(4fxohx2oNC;|}O z*s+-sigH1uapeZRSX5;8htanmgQE|VOw0Ut5m&DcpuMo049FS7@xZHEqCD#;%LQuz zB&>UUJ4d+(U5*YgFI{Dm|8%=2PB-iCm3;}($3k;qtq`gw?K{1c_Whly>6i{ZE}F^e z<@7Sw!>_$-W-UpdCqGR4*~~hpK8!Rx-uiq%aAQcTiAi6GGDIGWDBo&_4xYyz9h9x-aHDx zc)|BtSe4(l#ie7X)-FBjimV;r(`|yb`^PE~a#A7`8T~HzbqsJ5W8W+~wwbiF6NI{c zEHxuWUpX)}|&4>$C%8t#vyYf%C6kPTzm& zHwpQGX>2v504GhVDL?`OTqHlo@k*scOy2yI({Z^Zr1$ApbIIq}a3dp{d$Ti6ho3y5 z_x6aRk^*d?aVRHzax%kup~70uO(#>w+rJkQb}HqsN#Gi$2L zu^y?D-g?Fo4Q}uN-ubX?Gh(EUP?6s}TWbu?#lZ?MQJtG3=y@>3*87oHwt(LH(cqyL}aoe%R4UXb;%EK4AJKca5%GtL{}THJHu*>>ofpTst0p5H>&T)=iyf45|jyk16*rRjtlcC zddvj3u0nnyx|6JkFHelO;p-`}&>o5ma6= zzEp&rbPBL=2Tk`qli|db1x_Lszls~_0y9vOCiZb)ySQ(2Zx+@F?^=jw>6}hZ3lCHX zy!d934A$=KM2789VhFZBlhbvzF=S;rBO7JzQChlBej!*xwabEsWA`yH=Yc7d9zOG` zj^86cJUmvBVD-3?Slpw>Gmf&vA-GZcgvds4_J|eTT6ae)MVk+rH`&&lW_PCK*2DKM zRa=(bbN47WfcpT@$7lpHnS3=J+*Y8SJ4iy_0YWo#BH&Uh2Kr$~Be>Lt>GSXnbf=&+ z!yZl{6HZ+-Y$ZsR1GL@tH z>ejP41@925Tb!CF?sd&`hOZSe&yg(g*S{g5INDS!jA+)%v8c#TwCjnLz;mZH%)Q^u zN#4}4#;ex@H!kTD+RuTlg42Ca0no?zvL66KKkJVLXnQv;B;iSbgDwiz6|Eyk-do;)#3i zE($v)z)lQ$+iXzrID!ex(IkqAYuJISTd*ky*Zmu%gF0eW$aBXH@SQPs9o%HbK<@6` z_Sz@rrT($>wfNqahcnjEs4DDDmz^cT)7zy6E}Rfx%EWx>204YXoOr%6 zKD<_Od_6nTrvFlC!-}jy;L)r%)MjssgqVme+mb|6LZ929o)mJe!;Pc5e5{oX%Ou+) z$Tbuz=I108hYFKQCJTW}3wHZT6*0%4an!xNX>1EaLpo1rNbot{P+V>_-kg}&vwwfL zEb-yIUy-0qA>Lv>8I8{;Ms+*vVm2VvM5h@`f+!JGELoIz2isijrmC8z24ohTAZKht3qA>bt@-wJRBEep73w0f?J3EDKwE(h!}9Zr zWFzQ_idgW&RL`V&*IZ4^vsNhnTc?)g+=IL5`tRD#`xhMjUZb+P76j-2E+#D&vb9V4 zHBThA%MexGGsvJU9wOBpa=Nu78n5Z_$5bL?hu2Pfr+sr63aDTC?ySM5op;GYWm@)B zmhmTAmA+O8Wtcn{%Z0KeMwF3G-~|gGA=f(X9mZqX2j#0a6i^0b24R6AT;Ml#9Xcv5 zayKnG;%}L(R%Mi)r=_-mzH_4uTm&};{S>cJ0RC86f6SqPs)!A~Z!ckt^_KuJIToKjxhQ|c% zms;)B3o|jYy|LV*KFQs~7y6m9TgorI7HCF#bl+5afAfO2oQ3$1$%VKFpNwD2J$WgZ z0OouL4!Nvbf?nvI0rQN5D)HZIf=VpTT6Ae`#_PB|Y%5G?4Q;MYKAs0zz_6`xakUE) ze5Rj*M$0xE`uX$7*~SF3ad6MgXH{jO%%8Qiw7blizk~@AhkCKcg)}`+6v4A>`x3PY zf(XbmS)OQ)=bFcsJx5@Lj+o+v7+Z*X)^xs++|BmoC$g|BTodP>kK7_N=~+t7ZjDAh zn-ANz3tkZNMR%UTVs=^lgow)E*5u-C;9qZ``l`6mhefcFeE0KpJn&su?;N~aud6hn zB->RmKUU5^*Jh|y6`SwzeniI+H_z1di0IMF=hfFOAk@`(ISt9T%Mw^5h3qb zwYA2dFLoB#+KjiK<08gj^SL{?YW*(LWG1u=kYw@c+1KZe2}>gWbv7^fUJ2-p;-gYXup?y+;^SK-ir}Jb_Mm#> zitoqqs;Q%~-|l#I$t((gjOYg<=JB_Y2y()-DHz5v#7}_GzxW&*iE`i1Lm?J7XUezz z_bbBZ)7=;qTG{ZXw&QY(#Auu~<7y}|q0^O*Ak-;zJ(hvcGjOHV+G}xA&GCD6s-Oq~ z6OYD<#5i=G_;!9JmKML>FrELAPj~KGbzIEELM9tAR*05pD-+QsBc9ond(15Tku$BS zi`Jydvu`TAR)#ViP~kWOPO8y=-NgRRDye>?2I4KcW2wNC-#h9Oh*+ zby?PG4G8g?SKYO*#cEsF-PF2I5gp_({vzDQ5;RgJgg#94`>42 z_GI2MHFTnB{F`Zi#wScU7DBj?RKv~k*- z>w!Y#D^3V9FoPyE1F+|H=v^8=3=t9t?wKzG(Q$40eFvJ!?_V`UHK#@II z8O4E)5){Ys*91O(QOlPlbL5M{*5ASU^*9I|xA%@T3@@I?Hz$dCGXQU?`lwQPUKcUf z{u4s~IwzU}V&=n{*HFJCpB{NrqQ^g3_GX5m`8~d2sP_5|)hh$-(vYfuS)PB&2D4#D zv(@eGTx^W0^~H8Yf|Cqf*5j(B|FUT24bl@@&PWJ^=TMv$1^8<{{ZqPsr)a)iy&G(= zhU-2gUW5$>UcR!)D|DB%47m#?1EuB*1N7P}xnV6xgpu4kuK>r?k-(bsKQ75#z7=Zz z@fK)rqZGgcJWAL)Jc7p4f~!+HSYM#n3?qC}`)g6hnTlej6CW$Yj?vmfZ@l~{ zXrc_(>!ai`Jw7CQ@hn&mpg8o>39=npk#`gmx6IYn7zUDZlZvB!ymwKwbJL&6Fu_TQ zkApo8nhLgW#=L_s=*Ml?u!w?zrh7D2hjfN$fnVyIr{5q;`#Le-TZE-*Z6;%UUZ~A# zJ7n38*9*w6*RyaLE#Ropd8`kE1@p-{${W7psO`9f+Cq0&3;6rBq=8}zRjXphCKZb z49T|}bv;rKWmn<>_YvgG)9BNn0BKoO(vV!deIaffAC>6D!oG6mmfA9SSbYlgtalQC&_7ezQD~1d z%MqzAYr(dOl&zL)Uij?Rkq+*p(#Bi9N%_I9YWLT^r8rI;lThlf)oyLn(KNpCVB4g$VH+9LDep!uX9)8iN98CeC>HRIlh?Ii>vPu8kZVI0>24oK~Mot_1{ZF zy7l;pQVSDZnHhq|h%NY~h4P+2u|B@}tMx30iJ6Aaqu&`?8jq0z(Tin8wU`cNpgZ|utnO?>+^jYAZ;Ayk-(#&ZdgHNo9&i751z3^9iSP=Pl8 zGq3ztF^;zNrB-F8hu_0sxe2UZcub1u*Yu~Z9?-E?mCwffgy)B8Vv#DQ*FO$I&wxhn zGQWQ~RsxK9=g~itqdT);5G=>Jk!X^C%h~^Bl+b2})T)slJ^9;6x9-zk*ScQzHatgM z>#u8pycP88yLZ0~cm)NG>j~)ARh#5Q7(2M0%E5k3GrxcftQ|p-e5ecD-aI&^{?}17 z!E?>*Rel#w7Di-}3t5(6RMHp7M&~&mbiaGP6=(LX#$c)gQV&F+@&5^NkGI-Io=YZ) z#!7rL0}o=&tj}Z=_zsMSt*Yh%OFFzb_#+(=tgmSz$yhqGt67EKqyAd|4Tbyw>J-$- z?T~LGWC`H~pTksO-S)pX2dWN~9YJDjpvz6rxiJZ45KxP>c1^F6YDh7p3p}yF>m_>n zsoze1Qe>4CGf>1r87D96v90c&c4LSY>`{1JZY&qejQ?tWG^gR!@>&^~h??A0skvcb z4j~@7RKNS(8uBX0e>`3l9 zuZKFXQQP*O1r;4m@`Y6Y!N-5~zEB`|5L8%!AkihovHT(BQFMZ$ym47eUS3Ec5Axad z0oH}JC{MOqp>!YF;$pOHpzi-z-2ZAA6^j{|g7sDfqG|psfn~>fng?Exa({xNs|)f_6!b8PmSApjQ#+!Zwc=q&W8c8#x44HTG#i(LXV5@og2RQe5NNHPoiw$(S zezVgp$yI2)mH*O1BzKpV*ITl9_ORcETHg7RTjSP@#0-ZkudH)V+@kY57OLKxK4xIU zavF?CuujaDqVGIW;b$TUmRUkrw_WZ!1c}@EXVrjZ!B_7yOskU_Dt{)9_++93IdOv@ zJo6p@StJk*NwQ$z-%basO{|SCVU0LwuN#6D2Z^0v5 z5o<1DnYf~RK##{Y$7ZuZZ@nX|H0O>2$}9Q;2$Bf-2$*w4n}9n85sg2Kuz!=M*E&L2 zn+!BoaHz=y_LaF|3%2z>PP3XCY2BrYRPW5PDDq>sP)rW@6OVe<;1d<7HCfxjDRJ-a zt+!U@nwpYvTA6=0kI;XUJIDZ8mgw9SiD4wggdiTQ*jPia)~~qd%JPTblHssy^1P*f zma*!o%2Q^8j~C-$;Ab==vHVDGoWy3=CXMTPloP=!$14Jx&J}HWV!E$G5#f%IEQA>P zL0pk4s~ZpFRh}1Ln02u_`PuyAMW;BOi7r=}wlO_*jrT}ljdhBm#fMtZQI#`J0ttg{A9dRmso$VqwPMap{j4@OY-A}_aS?F3{Fv#jH zPg@R^^%*NvQH}ivM*UmPp;9Hw4)1W)+$K5>xZ}P!{Fjw;D9z6n?eFndBsW>=eEK`+ zKlSl;3GBTe(HHRfXF~lg`TvPxKwlcH{#ZAjMy%})p&=mB!2h*}eXA{58_s9{O$R3O zO+xe1i6faIdKW^<*z~pLiKg`|yGM1be;gdF-2-<$u!5tozGy zQ?C@_E8AuKVZY$pQ~kF0RlAxkd-E@S^uwEO=kb0baI#y%6=w0T@ZAIaGRlu7d?B~- zEUDp&^rVwS%%VL$xz~-8*pEBW^1yBN;$9b-HyT4rbT+|%&$xp{Q2UMFKtBKcY*1b* z*_m)u$k(i7^f;Q`f2Xol)X7`!)oGI`fMzjDwm3iRY<3^+@M}K_-Zr1+q8#LjW94`nB zS?VM)S4kR_JdUTe*vI{I!@hGD%V%lw(6oo;l?&O7d}eqa)g!ao#FD*g+3x3y65M+?SUsEE`9GACfv8H!5!^F&t#_ z+2-Ja+L(sHGgyHKns>nH@SkZ3jb$Zh_rNQuuo(7!OizJyjo=Goka!c2iNgQTkjF_5BBnz4YsiW_?i4`KDJmGfDhVt6q+>T@UGlsu+>lGPBWh4>HHfre08yJU1>Qh`n!F|0cq zhoE13e(#ICoIRc4$^^sb(?j#G*7$tFZkzm4bfbg-ZgmZlSM=EYox;&zMPVwje>+qv z0i{!w8F9E-X?UaNX=#T&G@kC2&e1S^A*$H7Fd991By_urI8SiveeO5j&P?bdDICeb zq1pU$#)B~_?Q*_zVt5(6U&C!UceWGeYJk&bPj%66cqmv*Os0Hs59LHUcP~6Y{PD^yT5De6kQ=^`XM2{7BvJPyozx@I&x7fZhn0P{EYY^ON9?1$bEC2WEJMcVV0B=!5FCZU#N@R%mA%2G;6F z@AULwNK%(I_l&gBSat%>K0h-oBY&>tZ?YL0NHP{@XX`{yw-D1|ENH=OKRl zNFbBJ1rIE2|0O=aDgzu$Von9-n@lUj^vjRO3D;ylEH3f(ngm;8^##z=ftaPwEPH$4 zKGspNEc&;{T_o{_1j!$N`{AVOA`2JWJ)7W&mZw7P8e%tBHJ8Bjuj}6@?U{3;=V;7B z<};X2Ph*qGxhzZI$JELT&euW~s_7C!0I@D7NrP;iOk(y-F#6KOM49%L;W(}8KkFR& zKNb`6o~&@exv_IIujFU|Q1PrussSso5E&s)#`2K34hj)1yk5m) zB12csbVe~YkbQgi>!Njr`Io9M;Y5joOZUGHTZpN)2tKv02T<~O+4HRg>x~H9=9P9B z(Qbji@-P!*(I-_h^W+dpCaes3xa%c`O9%W$HW3a@Q4_GG0Ov3{IPUm}mUIW#XN1zS zJ5T+{(nIyymXf3A&(A#sVw2`jB1A9jKBA*BS211(RyJ#i(|r0Vcg8wR{bwDL<|ynj zcOsk4Q*4hQvI_nmga6mwwZ}8Pzwymt3Y%NGm6dyh6cxz`A!&3YLM5SvA}p3Ua!m+H zCB~_&LMtH$88d`RlH79H$R)`pX|&DnL+AIaoO6D^bLr3DfA)EKp6~bjxjfJNxqO~y zBTuw<3E?>fTwmy0Gl!n!=$IL)NeYKQ0f>K7{AwT!dBBYnc7Gm9`9`9LU4esvxy%4a zW)UWdD>UQEu@~^LXKNIlFQMcOtu}YakDVg&w)4#iR@&uL>HB+{T?a|*AcLupL z9A~IZwia&gQmB_q8fg88(ym}NZ zYEJit)7v78yicCW6_M9?KpL5N+nJN1xw47ZK*k~lzxEabGLNI zb59-IrSYBTs_k2S`MfS|Wt>1h9ArMM*(~Bu6(8+gda%tKMO$wwk{1eZ)~$^iDc{IY zNf>Vm-!DToOEEj-uGT}iQcb&Qyan?aRYREEgyM*eQ060U1Yi$7?;|iRfM;nwXvYC2 zvguEA5oHjQmbpXONfOVdv$=N=4)A(f3dF4@NyG zYu}KnUg59^pY30!3QmRnf9Hd0|BElOoEV`?mUwix=nXE9CIz6)HdK2l);B32JzMF< zMWxe;Xv>>G?)u1yHXL}`~&v4q? zS?k$oY7;U}*luI=siO(n?`(9ZJx`5#-;Yv%(j_1jBHk4L^uw|&MA|=IdIX%2lg4nU zkY!I;CrYMYmY@cY+W2zgNi{<>-O+H?>03Ybe~{7`T-9^sb;%)sXIgpnc|0Xg*}6(6 z*XUvicE5RB049pb-m6z)gyfj8_N5>4KmD`Tm6Jg}mt@xj8oX3X-hME|+4NltQOhi& zDxMmO%fQ@cfn`WQ+} zD(+A`ek10t^t|@_o70c{&2;K<+;3hyIi)RrBo{fo6mRjG(5^;6Hvl&mKdx-Y*{YtU zk?%NL9kmg|k89DwxNwJhXM@+`$#pq~eyc!U+2d+{t6gd^(vRFDz(lXmXz{6##Z7f? zwmEZzNOhdFY+?JlJdE5wIT|Ujf2c(BY8qKus>b9U$YB#bxsD;gin8eGir*2DaYk60}hs&i`lx;kpPoW#wplRWs7~bPi zh9+JizKBQonJpQ$rSZOo+U}d+BNnle1TUN_j1;$=iGI76WuXgwWeYXt2L)Pf;mRkF zb0)zMiW!T$;89E{Hq;>2fdE5s*b;~uB#SR>6BotX2EyNMC=tTQ(lJONpqeL!*0I-! zdEGrCk9T9d*itw3W=N4UHKhw}|CM*WfL-Ol#ezPx@;_5b@g8;XC%}qACuhF-I&I!6P9JmYD&_X}S zGS9CA`SSh#^yvUB1_@FJ9~^TGjbr4?+j(fv==qbM^U*$i_v)7*FT&nno&D-S#FQIb zlql$lMt<~_;Wf|QzU)t2j$Z|`g;VkzaN0Ff1_IYv7=~6lV~BlQgmkuCwNGP<$2*>U zv@`Ql3>hS|MYV90Jq%|OvTU;8!)8D$hsv_C2bgExCuTcWojl2q$vf&*ZqC5I#CqZN z7%~}#>0?I+SN3?9uTgTK#ofDgFpnO##hNFITnzL+$Q$Sjygm!5pF_b=I(mkm$U(d$ zOltugY;33sGB_T9_)sSh9Ln=h7#;zX_c8)=Gl+f;%=x+?Od^-Bi3dN1=A;=fFXX|Q zC9J!tKDsS$(2?aP>$STAgs&)q;AxEiRC#>vnzIl{(2@7lu{oc|ccgI6N_mjYodvLK zOEciQ)-{J321U}C zIP$Px%rwtg2|Lj(JZ3Af(;?I9nI|k4Y`(wxaSK;SmIq~KS)ei}Ej~Vt!bdIA_ zA^xF4OmyUXJc^EuJw<)-ls+bFCGGMSb}Et7m3CYxOJGo`PvrV0X>hL6e=~mzAMvL) zL0^SBPNRCCs|fydkT*&Cy=J*8*+z(4z@4&^&p7e8%Vqew_d&!fVUo1_!NB=TthMcU ziw9ids+WcsRjN%8J%Ci5Zr$xvT}r09%5zL!lJT0}H)>8QU&^L;OVtFyX%7{ep{2$5 zpP%1dYNgOov*eyJl6Y57JF3(?`dwrHNoMbqk1G;yewJSIuo5Z1KeV2wnzD_Vg12n- z4rq#+F*CSkw0dI?r2JJ8H&A4ILqq3_MK1!J>WggljKru=vXCWOnxT4kf9d|PFgS<%!_oDsrd zfD#`AK9Q^EAVxoEi}1MLwr?Zqt=9pZP=kOgQf@UAzpY7IMCD(2jJrVISl!OMnHOxN&k9m z{%HeJlqhl;F3W0g*dQ-5NA_vkwc*{gq{!GC^$8nn{($p{#vKX)?&` zk$Z%xUgwy&cr=Hx`3!Pzcu<5_oBZKw-3o=01pVy;iuH>g=Z80!$a%tmZ~j&hs;g;g zUjf@Ri0Nz3uD;FIuHC>B^o5MdMxcc`ZVZ#GW7oIy>RRQcrObE@tu_zjVi@kFAyy7+ zausuyh8Z0|%MuQkQQ*8r7lBs7G3TS>*4#^V)hs$$tTXi4%&T;k0yk9Fb;LWnYee2K z(IgYtjT>APn7rtlI17ee&e>!2{jSKnH$B#S)!*4E37dqQaVZpcVk}RE`7Exl3H27F zG~YZHC*V#g2ejx&ZFX2mTj=ixB%-x(@8vG z!tl|7y?}ig~V==A!gc znWfy&`BrB*&T|&hZqP3yl3Mg6NXCSeW|OD@ePkc%mma2UY=CBafy;8J$@Sd?`W7dOT|1$TAnaf3^fwT{xf&#fA^GG@b5t&)~foFWpSqj)=W>YOj)Pj+MY1xu6u&utFy3 z{x3TPUqivB5D($8(C|hlE%=u@=?>NI=GhT z2J;Mlt)bW5dj5Hp%vG!)jQZ+}6{p22}J_G0#q08Fe+N%q$u`mcPU-S<*bKWLc4;)bQ=Ab!}>XnX9X0 z^RtXMpYYt5NqqC#_O7O(gLmbO>;Xy320Nc6g{&Ui^DBD8j%Y1LETyFuCr{ri!XB3< zcwj42bRW^g4JDqee+_k8FWL-U%?{H=6^}r}-8_xMClDzSeZFM4MmJF^*nQw)QSX)K zBcef1mf^J#@AGrvZe7Jz=^u5rhK9Q?`;>U5wPas+-lnW|QrG;1o?x9Sbnd1#?+WQP zPO#Xzkrt&-5&YoNy3!|*Qceo)o6?&)A!;YM?UP7C z8Yl|k)p50kdB*ZvInkSL^J$f3wVX0Dd!M>a|8m zf1j!fcMA(=b$v|hD_JB~K3lF|?4qI6OU28F&dBwzJJ#BA2X3t+H5|&~Bb&2YRtg_X z9kvNn5rrWNR?D~%FBI^^K4=s}BDNPCR8OEE*qqoM?~=fW(26`;wTK#TP0LV3vLAI^ z-Z8t<XJJ#L zubh^FLiGz;94#bX*c9h0u>?5wfcQ5pau#w}*jMH&2XWzf4hy@?EM#E2psfB^275rC znz>i_Pm%qFqzem~eAxv!{)NjQSB3t{Wtq$$)`(vCD&K44uPh*te(X0) o{;ivTL;jsO{g(WY&o|`13Rhceey~O6ng$9%YJmDzJvsO5U(>ik$N&HU literal 0 HcmV?d00001 diff --git a/tests/extensions/tabular/test_xlsx_tools.py b/tests/extensions/tabular/test_xlsx_tools.py index 1cecf369e..6c42ccb1b 100644 --- a/tests/extensions/tabular/test_xlsx_tools.py +++ b/tests/extensions/tabular/test_xlsx_tools.py @@ -1,5 +1,7 @@ import os +import pytest + from mfr.extensions.tabular.libs import xlrd_tools BASE = os.path.dirname(os.path.abspath(__file__)) @@ -17,4 +19,34 @@ def test_xlsx_xlrd(self): assert sheet[0][2] == {'field': 'three', 'id': 'three', 'name': 'three', 'sortable': True} assert sheet[1][0] == {'one': 'a', 'two': 'b', 'three': 'c'} assert sheet[1][1] == {'one': 1.0, 'two': 2.0, 'three': 3.0} - assert sheet[1][2] == {'one': u'wierd\\x97', 'two': u'char\\x98','three': u'set\\x99'} + assert sheet[1][2] == {'one': u'wierd\\x97', 'two': u'char\\x98', 'three': u'set\\x99'} + + def test_xlsx_xlrd_duplicate_fields(self): + with open(os.path.join(BASE, 'files', 'test_duplicate.xlsx')) as fp: + sheets = xlrd_tools.xlsx_xlrd(fp) + + sheet = sheets.popitem()[1] + assert sheet[0][0] == {'id': 'Name', 'name': 'Name', 'field': 'Name', 'sortable': True} + assert sheet[0][1] == {'id': 'Dup (1)', 'name': 'Dup (1)', + 'field': 'Dup (1)', 'sortable': True} + assert sheet[0][2] == {'id': 'Dup (2)', 'name': 'Dup (2)', + 'field': 'Dup (2)', 'sortable': True} + assert sheet[0][3] == {'id': 'Dup (3)', 'name': 'Dup (3)', + 'field': 'Dup (3)', 'sortable': True} + assert sheet[0][4] == {'id': 'Dup (4)', 'name': 'Dup (4)', + 'field': 'Dup (4)', 'sortable': True} + assert sheet[0][5] == {'id': 'Not Dup', 'name': 'Not Dup', + 'field': 'Not Dup', 'sortable': True} + assert sheet[1][0] == {'Name': 1.0, 'Dup (1)': 2.0, 'Dup (2)': 3.0, + 'Dup (3)': 4.0, 'Dup (4)': 5.0, 'Not Dup': 6.0} + + # After demo it was suggested the iteration cap be raised. The value ended up to be about 5,000 + # Unsure best way to test this case with such a high cap. Going to leave the test for now + # def test_xlsx_xlrd_duplicate_fields_handle_naming(self): + # with open(os.path.join(BASE, 'files', 'test_duplicate_uuid.xlsx')) as fp: + # sheets = xlrd_tools.xlsx_xlrd(fp) + # print(sheets) + # sheet = sheets.popitem()[1] + + # assert sheet[0][1]['field'] != 'dup (12)' + # assert len(sheet[0][1]['field']) > 24 From 36771a935ec5395b88cccce4deef834c13c513fe Mon Sep 17 00:00:00 2001 From: Addison Schiller Date: Thu, 16 Nov 2017 14:54:45 -0500 Subject: [PATCH 2/3] `max_iterations` default arg for testing --- mfr/extensions/tabular/libs/xlrd_tools.py | 4 ++-- tests/extensions/tabular/test_xlsx_tools.py | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/mfr/extensions/tabular/libs/xlrd_tools.py b/mfr/extensions/tabular/libs/xlrd_tools.py index ca0c19fe0..7c8efcc70 100644 --- a/mfr/extensions/tabular/libs/xlrd_tools.py +++ b/mfr/extensions/tabular/libs/xlrd_tools.py @@ -8,7 +8,7 @@ from mfr.extensions.tabular.exceptions import TableTooBigError -def xlsx_xlrd(fp): +def xlsx_xlrd(fp, max_iterations=5000): """Read and convert a xlsx file to JSON format using the xlrd library. :param fp: File pointer object :return: tuple of table headers and data @@ -53,7 +53,7 @@ def xlsx_xlrd(fp): iteration = 0 while increased_name in fields: iteration += 1 - if iteration > 5000: + if iteration > max_iterations: increased_name = name + ' ({})'.format(uuid.uuid4()) else: counts[name] += 1 diff --git a/tests/extensions/tabular/test_xlsx_tools.py b/tests/extensions/tabular/test_xlsx_tools.py index 6c42ccb1b..fa8e09456 100644 --- a/tests/extensions/tabular/test_xlsx_tools.py +++ b/tests/extensions/tabular/test_xlsx_tools.py @@ -40,13 +40,13 @@ def test_xlsx_xlrd_duplicate_fields(self): assert sheet[1][0] == {'Name': 1.0, 'Dup (1)': 2.0, 'Dup (2)': 3.0, 'Dup (3)': 4.0, 'Dup (4)': 5.0, 'Not Dup': 6.0} - # After demo it was suggested the iteration cap be raised. The value ended up to be about 5,000 - # Unsure best way to test this case with such a high cap. Going to leave the test for now - # def test_xlsx_xlrd_duplicate_fields_handle_naming(self): - # with open(os.path.join(BASE, 'files', 'test_duplicate_uuid.xlsx')) as fp: - # sheets = xlrd_tools.xlsx_xlrd(fp) - # print(sheets) - # sheet = sheets.popitem()[1] - - # assert sheet[0][1]['field'] != 'dup (12)' - # assert len(sheet[0][1]['field']) > 24 + def test_xlsx_xlrd_duplicate_fields_handle_naming(self): + with open(os.path.join(BASE, 'files', 'test_duplicate_uuid.xlsx')) as fp: + sheets = xlrd_tools.xlsx_xlrd(fp, max_iterations=10) + + sheet = sheets.popitem()[1] + + # this if you raise max iterations, it will be named dup (13) instead of dup () + assert sheet[0][1]['field'] != 'dup (13)' + # using `len` is an easy way to see the uuid has been appended + assert len(sheet[0][1]['field']) > 24 From e4fec4764366554319c357a176290df4d4720367 Mon Sep 17 00:00:00 2001 From: Addison Schiller Date: Thu, 16 Nov 2017 15:04:42 -0500 Subject: [PATCH 3/3] Style changes --- mfr/extensions/tabular/libs/xlrd_tools.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/mfr/extensions/tabular/libs/xlrd_tools.py b/mfr/extensions/tabular/libs/xlrd_tools.py index 7c8efcc70..8a5983f6d 100644 --- a/mfr/extensions/tabular/libs/xlrd_tools.py +++ b/mfr/extensions/tabular/libs/xlrd_tools.py @@ -40,13 +40,13 @@ def xlsx_xlrd(fp, max_iterations=5000): duplicate_fields = set([x for x in fields if fields.count(x) > 1]) if len(duplicate_fields): counts = {} - for name in duplicate_fields: - counts[name] = 1 + for field in duplicate_fields: + counts[field] = 1 + + for i, field in enumerate(fields): + if field in duplicate_fields: + increased_name = field + ' ({})'.format(counts[field]) - for x in range(len(fields)): - if fields[x] in duplicate_fields: - name = fields[x] - increased_name = name + ' ({})'.format(counts[name]) # this triggers if you try to rename a header, and that new name # already exists in fields. it will then increment to look for the # next available name. @@ -54,12 +54,12 @@ def xlsx_xlrd(fp, max_iterations=5000): while increased_name in fields: iteration += 1 if iteration > max_iterations: - increased_name = name + ' ({})'.format(uuid.uuid4()) + increased_name = field + ' ({})'.format(uuid.uuid4()) else: - counts[name] += 1 - increased_name = name + ' ({})'.format(counts[name]) + counts[field] += 1 + increased_name = field + ' ({})'.format(counts[field]) - fields[x] = increased_name + fields[i] = increased_name data = [] for i in range(1, sheet.nrows): row = []