From ed91cddea59ce15da87ab744ac20b465a36ed5ef Mon Sep 17 00:00:00 2001 From: Thomas Charbonnel Date: Thu, 11 Aug 2022 00:20:48 +0800 Subject: [PATCH] This closes #1296, add new function `GetRowOpts` for stream reader (#1297) - Support get rows properties by `GetRowOpts` function - New exported constant `MaxCellStyles` --- rows.go | 24 ++++++++++++++++++++++++ rows_test.go | 24 ++++++++++++++++++++++++ sheet.go | 28 ++++++++++++++++++++++++++++ sheet_test.go | 26 ++++++++++++++++++++++++++ test/Book1.xlsx | Bin 20738 -> 20451 bytes xmlDrawing.go | 1 + 6 files changed, 103 insertions(+) diff --git a/rows.go b/rows.go index 853c8f7..457f59b 100644 --- a/rows.go +++ b/rows.go @@ -80,12 +80,14 @@ type Rows struct { sst *xlsxSST decoder *xml.Decoder token xml.Token + curRowOpts, seekRowOpts RowOpts } // Next will return true if find the next row element. func (rows *Rows) Next() bool { rows.seekRow++ if rows.curRow >= rows.seekRow { + rows.curRowOpts = rows.seekRowOpts return true } for { @@ -101,6 +103,7 @@ func (rows *Rows) Next() bool { rows.curRow = rowNum } rows.token = token + rows.curRowOpts = extractRowOpts(xmlElement.Attr) return true } case xml.EndElement: @@ -111,6 +114,11 @@ func (rows *Rows) Next() bool { } } +// GetRowOpts will return the RowOpts of the current row. +func (rows *Rows) GetRowOpts() RowOpts { + return rows.curRowOpts +} + // Error will return the error when the error occurs. func (rows *Rows) Error() error { return rows.err @@ -151,6 +159,8 @@ func (rows *Rows) Columns(opts ...Options) ([]string, error) { } else if rows.token == nil { rows.curRow++ } + rows.token = token + rows.seekRowOpts = extractRowOpts(xmlElement.Attr) if rows.curRow > rows.seekRow { rows.token = nil return rowIterator.columns, rowIterator.err @@ -170,6 +180,20 @@ func (rows *Rows) Columns(opts ...Options) ([]string, error) { return rowIterator.columns, rowIterator.err } +func extractRowOpts(attrs []xml.Attr) RowOpts { + rowOpts := RowOpts{Height: defaultRowHeight} + if styleID, err := attrValToInt("s", attrs); err == nil && styleID > 0 && styleID < MaxCellStyles { + rowOpts.StyleID = styleID + } + if hidden, err := attrValToBool("hidden", attrs); err == nil { + rowOpts.Hidden = hidden + } + if height, err := attrValToFloat("ht", attrs); err == nil { + rowOpts.Height = height + } + return rowOpts +} + // appendSpace append blank characters to slice by given length and source slice. func appendSpace(l int, s []string) []string { for i := 1; i < l; i++ { diff --git a/rows_test.go b/rows_test.go index 4fe2851..4b57c34 100644 --- a/rows_test.go +++ b/rows_test.go @@ -96,6 +96,30 @@ func TestRowsIterator(t *testing.T) { assert.Equal(t, expectedNumRow, rowCount) } +func TestRowsGetRowOpts(t *testing.T) { + sheetName := "Sheet2" + expectedRowStyleID1 := RowOpts{Height: 17.0, Hidden: false, StyleID: 1} + expectedRowStyleID2 := RowOpts{Height: 17.0, Hidden: false, StyleID: 0} + expectedRowStyleID3 := RowOpts{Height: 17.0, Hidden: false, StyleID: 2} + f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) + require.NoError(t, err) + + rows, err := f.Rows(sheetName) + require.NoError(t, err) + + rows.Next() + rows.Columns() // Columns() may change the XML iterator, so better check with and without calling it + got := rows.GetRowOpts() + assert.Equal(t, expectedRowStyleID1, got) + rows.Next() + got = rows.GetRowOpts() + assert.Equal(t, expectedRowStyleID2, got) + rows.Next() + rows.Columns() + got = rows.GetRowOpts() + assert.Equal(t, expectedRowStyleID3, got) +} + func TestRowsError(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if !assert.NoError(t, err) { diff --git a/sheet.go b/sheet.go index 01dd167..1f2dcea 100644 --- a/sheet.go +++ b/sheet.go @@ -928,6 +928,34 @@ func attrValToInt(name string, attrs []xml.Attr) (val int, err error) { return } +// attrValToFloat provides a function to convert the local names to a float64 +// by given XML attributes and specified names. +func attrValToFloat(name string, attrs []xml.Attr) (val float64, err error) { + for _, attr := range attrs { + if attr.Name.Local == name { + val, err = strconv.ParseFloat(attr.Value, 64) + if err != nil { + return + } + } + } + return +} + +// attrValToBool provides a function to convert the local names to a boolean +// by given XML attributes and specified names. +func attrValToBool(name string, attrs []xml.Attr) (val bool, err error) { + for _, attr := range attrs { + if attr.Name.Local == name { + val, err = strconv.ParseBool(attr.Value) + if err != nil { + return + } + } + } + return +} + // SetHeaderFooter provides a function to set headers and footers by given // worksheet name and the control characters. // diff --git a/sheet_test.go b/sheet_test.go index c68ad31..9b0caf4 100644 --- a/sheet_test.go +++ b/sheet_test.go @@ -505,3 +505,29 @@ func newSheetWithSave() { } _ = file.Save() } + +func TestAttrValToBool(t *testing.T) { + _, err := attrValToBool("hidden", []xml.Attr{ + {Name: xml.Name{Local: "hidden"}}, + }) + assert.EqualError(t, err, `strconv.ParseBool: parsing "": invalid syntax`) + + got, err := attrValToBool("hidden", []xml.Attr{ + {Name: xml.Name{Local: "hidden"}, Value: "1"}, + }) + assert.NoError(t, err) + assert.Equal(t, true, got) +} + +func TestAttrValToFloat(t *testing.T) { + _, err := attrValToFloat("ht", []xml.Attr{ + {Name: xml.Name{Local: "ht"}}, + }) + assert.EqualError(t, err, `strconv.ParseFloat: parsing "": invalid syntax`) + + got, err := attrValToFloat("ht", []xml.Attr{ + {Name: xml.Name{Local: "ht"}, Value: "42.1"}, + }) + assert.NoError(t, err) + assert.Equal(t, 42.1, got) +} diff --git a/test/Book1.xlsx b/test/Book1.xlsx index 6a497e33afb45a5764a323326bd1f2f57480184d..ed3e29295461c903ad4511cc7d434b18297b293f 100644 GIT binary patch delta 2144 zcmaJ>YgAKL7QQzm*1jaWA^0;6~1r(4ef`~#3R#4RNl2@yPMhBFhP-aQinl(S}S?Al|x4*sjJ!|ch zbEqaCrHlOy@MZ`?A|Yr*JwweXf>e^r=^E5xJ|+H2R6l|shyVwexc7uv3Z(OC z7~ddEUzx-!+wKib2-oML2-bVwK$k!R?1Uj*pPMG0eAfawB?_LBEJ2z-1xyg{ z&Q&V#r-4Dz*c=*%$W&m4nNnU6=0a=%4O?R}W;h2E1T>ILHvwU^S05u-G99O1Qyy$v z%ON00!740oaWFw|;=OMNV-Y-poMT}$>m_(S?nmmaAL($$-%xDJvNigiXBvCzYv3Fu zC@8fZ(o1V9xr5=7VXEcd;`F2&rO#hhkPw7N#`f6XBQfT`#lz`@ z4ghWbV}KxL2>2kfz+}#u|FyFUpkZs*Z8p%eQ$?q;Va+#)P zj&@}8uUS)lM8rct+3VMGytipWK9$ThX;lX7K}D%XEk))&R2nXbwd&$8k%XD|hqOl) z9?hucC)C?lq_q3oQjT(Ve7Sd;+n>rSFtIOBQJ?m53~JjBk! z<#{t%Bn;Z(k~bdq4C$-_V+tbfp zzIBfr2;`1Ma|bZ=Sb@OehIf<86B=JjrNnpav}{aaM>k~c$jV(&eM!7c*6)+?s)BT(&4l3 z%zJGD+Qtq`gMBN6Geu*9#n-RnK2(GK9BRp&mvJZSpRtJsUfjo?Yz;w<6)SyO$LjI7}E+|HFn zcuUA+BDLHdi<1~}H*b7iR%+{`;a~D*mF6W{DYTR3`@RTh>U38r8v-YOb?9}@>8ZLB zbvUNK&987sC2qI;xh{6<(;TgO=)sDl{e&dh$bp&sLnqX1mT&3l-Q$@q41vuVW!D9T zyj$Do6H(FAINUHI99ClIa~=lf*M9KG?NZ#yjlY3kJN$K$sbYe-n^D%GO~GB`Jds+4 zDiYR{Zek6->8_h14;Kcuo{&?$+RBm-NI6f~#CP@Yt!mQTqBONr=IWOCzU?+MFL&*( zs(Dz|JZ#rL71)2Nr8V{OBI^B5|Cx2Z^QQE4s`cF_!DQOtBhD``&IVsz*r0st`Qi4o z=Sq}vD(k^jX?A!1(J%ioCVBERSrrv@*tR5d>D8;tpFIlt#^+yWVvS{vBl~_I<@BLq ze-vj1ml6WZ_?{>NLitSkJGdhv68FT)*sFEtM@c%Li7o(Td`l?23-9${g1-dbjr^H7 zXG5SgTtfaR4u-)v6v@F z5HM}BEfC~jEPIzEGI76*y-OM^O_Vw6me9wlP=>3ff$mu&Ed*V3zz{BluC78R>H)S1 zbrs}7CN9ASZuF@9VVln2`7K?09J0KIWpdbqh+&%*}BAsUs z1WYHm&~?BnfQcJ)2A%;l2*Mx;;{bVpj>(1qxPTc~^LeH{Fqq>GJ_Ia=3c8qyyX*-z zigi6iLqPQcrQVEHy1|wjz~}j&YuFXW5d^*#a^a}T2PT-?&Fy5upF8>g?PD+`{)ECH KA_&IQx%>ecMfd^$ delta 2774 zcma);dpwl+9>?c~$2AG#HuNyq#+~D~MH$6h7!zCA?6%`t%ot`Un=);YZBKG}Qqc;r z#D+3XvQbEzERF5;3cDhbOD-u%DRO=@TP^3j%sJ0L&+YyF{662$?|J=x&rm-4$OW`6 zla9ryqaX-Exv+EnP|J>WNIymMicK5ZL=FnN-#glhMxkotQK;1_khPdafHDsCsPKDvC0W1{nkJ2&%b-yG+N;Y7%h$|^ z+48D_+AdiXN|mrWi*({OkycHy4L$YJixa>`w3G&gop?wb#{b1W)AEx6Y@#2r!4X(4 zFkjt<&|hB<2iWj8BWx57g}PuYg*|R)E%xxsy2f9$K)Tb?5*8Va0`%;y-llrcT7?u; z*iICQWp9^XRHy>n5=d^fi4$HiIf()61q*aV6!>;t0n4khJ2u>d0K1X<_(e5@LQ$lA z?m}nVDzS&2h1mND@bFgJRJ^wf!U9(RnFY=ha8`;^Jj6H}i*;*R_KEU9w@+FQKReBc zr|l*4^SW*@ZOJ-PbcL>SVqLc{EGr%XO>Jp8+}yHL%zD%oTrvZ!qBPcwEXjASQ{6Ti ztDBuB6vNikHwHMV6^`gKq2DhBohg;6RL%cgAluC|Exkl0=T1JE zImSBmw4glhkm%fX4MTft$ZVgYYzIH{H8heSP?C$c-Bojm5tq$(j0r-@27q@p3{(vER)YEuEOz$Upi0kc-8Van5H?axy#(Z#f?@zilwfOTSyD%Gobf5We<8ha)-BFuV>zw z4vcS{Ae30|YPqJImNxF7ZDHevx$9hnPkqYlUmw43kc2yV`dFJInYg}MJZkk`u>6VwWDj{&wxn$hcj7BvzrIa^? z#>C4h<`!@hlcMFVDF28^tVszt{;at$qjkK;6e9PO$ zxl9Y(m&23`g;@~8EGIp?K0)~E;Y<9r3cKv~)Q9L3CUtm0)!^4X56^FF(DHQ`*iah| zD!;1UMrgZQb$K`=QQOD&jw7?y@z9dU#Cj*2TNm%~ zMrDGXup_Wht-$~FpDAPEglGLz3kE)q@zY0NTE6JZTY7IgM!l6NNL!eAojE)=-U;9G zhadSE)&_npr*}ga<%#AP^bP73HBS~BgdE&cdP-}sf%t{ zD9M>unhomo5Is@eUrbIS=Iv-5H3@IaI zS&`5fbrkp+K&>IYEQKi)zDvQQI`Sko0SBs{W-E+#%c2+%?V zk_Y*87(NE};{>)<$Q~9ij&H?}`<5MMv(nxmpqBLii?Urw8F|43H>1BOE{78q9moF< z9hMi2IYUH}y)1|y|FD*CSy~^%m7I%>kxrrxGVLYqq`(A^tnpSvnRJ_L4G5pZpaaWHI2a zXPqQ(<5Dqm~|_B4>==MP8!SR@hAhfX%f&Fa~i)tjId%pBniv=JJnoWmcXZpg=>sl^`8HN(zHu|5v1mUHu!X C@kS&7 diff --git a/xmlDrawing.go b/xmlDrawing.go index 8c3d734..b4fdccc 100644 --- a/xmlDrawing.go +++ b/xmlDrawing.go @@ -107,6 +107,7 @@ const ( MaxFieldLength = 255 MaxColumnWidth = 255 MaxRowHeight = 409 + MaxCellStyles = 64000 MinFontSize = 1 TotalRows = 1048576 MinColumns = 1