@@ -16,8 +16,10 @@ import (
1616 "archive/zip"
1717 "bytes"
1818 "encoding/xml"
19+ "fmt"
1920 "io"
2021 "os"
22+ "path"
2123 "path/filepath"
2224 "strconv"
2325 "strings"
@@ -26,6 +28,8 @@ import (
2628 "golang.org/x/net/html/charset"
2729)
2830
31+ const targetModeExternal = "external"
32+
2933// File define a populated spreadsheet file struct.
3034type File struct {
3135 mu sync.Mutex
@@ -39,6 +43,7 @@ type File struct {
3943 streams map [string ]* StreamWriter
4044 tempFiles sync.Map
4145 xmlAttr sync.Map
46+ tableRefs sync.Map
4247 CalcChain * xlsxCalcChain
4348 CharsetReader charsetTranscoderFn
4449 Comments map [string ]* xlsxComments
@@ -59,6 +64,19 @@ type File struct {
5964 WorkBook * xlsxWorkbook
6065}
6166
67+ type tableRef struct {
68+ ref string
69+ sheet string
70+ columns []string
71+ }
72+
73+ type relationMetadata struct {
74+ wb * xlsxWorkbook
75+ wbRels * xlsxRelationships
76+ relsPerSheet map [string ]* xlsxRelationships
77+ tables map [string ]* xlsxTable
78+ }
79+
6280// charsetTranscoderFn set user-defined codepage transcoder function for open
6381// the spreadsheet from non-UTF-8 encoding.
6482type charsetTranscoderFn func (charset string , input io.Reader ) (rdr io.Reader , err error )
@@ -140,6 +158,7 @@ func newFile() *File {
140158 checked : sync.Map {},
141159 sheetMap : make (map [string ]string ),
142160 tempFiles : sync.Map {},
161+ tableRefs : sync.Map {},
143162 Comments : make (map [string ]* xlsxComments ),
144163 Drawings : sync.Map {},
145164 sharedStringsMap : make (map [string ]int ),
@@ -204,6 +223,10 @@ func OpenReader(r io.Reader, opts ...Options) (*File, error) {
204223 for k , v := range file {
205224 f .Pkg .Store (k , v )
206225 }
226+
227+ if err := f .storeRelations (file ); err != nil {
228+ return f , err
229+ }
207230 if f .CalcChain , err = f .calcChainReader (); err != nil {
208231 return f , err
209232 }
@@ -217,6 +240,136 @@ func OpenReader(r io.Reader, opts ...Options) (*File, error) {
217240 return f , err
218241}
219242
243+ func (f * File ) storeRelations (files map [string ][]byte ) error {
244+ relMetadata , err := f .parseRelationMetadata (files )
245+ if err != nil {
246+ return err
247+ }
248+ if relMetadata .wb == nil || relMetadata .wbRels == nil {
249+ return nil
250+ }
251+
252+ sheetRelIDs := make (map [string ]string )
253+ for _ , sheet := range relMetadata .wb .Sheets .Sheet {
254+ sheetRelIDs [sheet .ID ] = sheet .Name
255+ }
256+
257+ sheetBaseToSheetNames := make (map [string ]string )
258+ for _ , rel := range relMetadata .wbRels .Relationships {
259+ sheetName , ok := sheetRelIDs [rel .ID ]
260+
261+ if ! ok || strings .ToLower (rel .TargetMode ) == targetModeExternal || rel .Type != SourceRelationshipWorkSheet {
262+ continue
263+ }
264+
265+ sheetBaseToSheetNames [fmt .Sprintf ("%s.rels" , path .Base (rel .Target ))] = sheetName
266+ }
267+
268+ tableBaseToSheetNames := make (map [string ]string )
269+ for key , sheetRels := range relMetadata .relsPerSheet {
270+ sheetName , ok := sheetBaseToSheetNames [key ]
271+ if ! ok {
272+ continue
273+ }
274+
275+ for _ , rel := range sheetRels .Relationships {
276+ if strings .ToLower (rel .TargetMode ) == targetModeExternal || rel .Type != SourceRelationshipTable {
277+ continue
278+ }
279+
280+ tableBaseToSheetNames [path .Base (rel .Target )] = sheetName
281+ }
282+ }
283+
284+ for key , t := range relMetadata .tables {
285+ if sheetName , ok := tableBaseToSheetNames [key ]; ok {
286+ f .tableRefs .Store (t .Name , tableRefFromXLSXTable (t , sheetName ))
287+ }
288+ }
289+
290+ return nil
291+ }
292+
293+ func (f * File ) parseRelationMetadata (files map [string ][]byte ) (* relationMetadata , error ) {
294+ var err error
295+ relMetadata := & relationMetadata {
296+ relsPerSheet : map [string ]* xlsxRelationships {},
297+ tables : map [string ]* xlsxTable {},
298+ }
299+
300+ for k , v := range files {
301+ switch {
302+ case strings .Contains (k , "xl/workbook.xml" ) && v != nil :
303+ relMetadata .wb , err = f .parseWorkbook (v )
304+ if err != nil {
305+ return nil , err
306+ }
307+ case strings .Contains (k , "xl/_rels/workbook.xml.rels" ) && v != nil :
308+ relMetadata .wbRels , err = f .parseRelationships (v )
309+ if err != nil {
310+ return nil , fmt .Errorf ("workbook rels: %w" , err )
311+ }
312+ case strings .Contains (k , "xl/worksheets/_rels" ) && v != nil :
313+ sheetRels , err := f .parseRelationships (v )
314+ if err != nil {
315+ return nil , fmt .Errorf ("workbook sheet rel %s: %w" , k , err )
316+ }
317+ relMetadata .relsPerSheet [path .Base (k )] = sheetRels
318+ case strings .Contains (k , "xl/tables" ) && v != nil :
319+ table , err := f .parseTable (v )
320+ if err != nil {
321+ return nil , fmt .Errorf ("table %s: %w" , k , err )
322+ }
323+ relMetadata .tables [path .Base (k )] = table
324+ }
325+ }
326+
327+ return relMetadata , nil
328+ }
329+
330+ func (f * File ) parseWorkbook (v []byte ) (* xlsxWorkbook , error ) {
331+ var wb * xlsxWorkbook
332+
333+ dec := f .xmlNewDecoder (bytes .NewReader (namespaceStrictToTransitional (v )))
334+ if err := dec .Decode (& wb ); err != nil && err != io .EOF {
335+ return nil , fmt .Errorf ("decoding workbook: %w" , err )
336+ }
337+
338+ return wb , nil
339+ }
340+
341+ func (f * File ) parseRelationships (v []byte ) (* xlsxRelationships , error ) {
342+ var rels * xlsxRelationships
343+
344+ dec := f .xmlNewDecoder (bytes .NewReader (namespaceStrictToTransitional (v )))
345+ if err := dec .Decode (& rels ); err != nil && err != io .EOF {
346+ return nil , fmt .Errorf ("decoding relationships: %w" , err )
347+ }
348+
349+ return rels , nil
350+ }
351+
352+ func (f * File ) parseTable (v []byte ) (* xlsxTable , error ) {
353+ var table * xlsxTable
354+ dec := f .xmlNewDecoder (bytes .NewReader (namespaceStrictToTransitional (v )))
355+ if err := dec .Decode (& table ); err != nil && err != io .EOF {
356+ return nil , fmt .Errorf ("parsing table: %w" , err )
357+ }
358+ return table , nil
359+ }
360+
361+ func tableRefFromXLSXTable (t * xlsxTable , sheet string ) tableRef {
362+ tblRef := tableRef {
363+ ref : t .Ref ,
364+ sheet : sheet ,
365+ columns : make ([]string , 0 , t .TableColumns .Count ),
366+ }
367+ for _ , col := range t .TableColumns .TableColumn {
368+ tblRef .columns = append (tblRef .columns , col .Name )
369+ }
370+ return tblRef
371+ }
372+
220373// getOptions provides a function to parse the optional settings for open
221374// and reading spreadsheet.
222375func (f * File ) getOptions (opts ... Options ) * Options {
0 commit comments