package anyxcelize import ( "bytes" "encoding/xml" "errors" "fmt" // "io" // "math" "strings" "time" "os" "strconv" // "github.com/mohae/deepcopy" ) // Functions: cols // Rows return the current column's row values. func (cols *ColsGeneric) RowsGeneric(opts ...Options) ([]any, error) { var rowIterator rowXMLIteratorGeneric if cols.stashCol >= cols.curCol { return rowIterator.cells, rowIterator.err } cols.rawCellValue = parseOptions(opts...).RawCellValue if cols.sst, rowIterator.err = cols.f.sharedStringsReader(); rowIterator.err != nil { return rowIterator.cells, rowIterator.err } decoder := cols.f.xmlNewDecoder(bytes.NewReader(cols.sheetXML)) for { token, _ := decoder.Token() if token == nil { break } switch xmlElement := token.(type) { case xml.StartElement: rowIterator.inElement = xmlElement.Name.Local if rowIterator.inElement == "row" { rowIterator.cellCol = 0 rowIterator.cellRow++ attrR, _ := attrValToInt("r", xmlElement.Attr) if attrR != 0 { rowIterator.cellRow = attrR } } if cols.rowXMLHandlerGeneric(&rowIterator, &xmlElement, decoder); rowIterator.err != nil { return rowIterator.cells, rowIterator.err } case xml.EndElement: if xmlElement.Name.Local == "sheetData" { return rowIterator.cells, rowIterator.err } } } return rowIterator.cells, rowIterator.err } func (f *File) ColsGeneric(sheet string) (*ColsGeneric, error) { name, ok := f.getSheetXMLPath(sheet) if !ok { return nil, ErrSheetNotExist{sheet} } if ws, ok := f.Sheet.Load(name); ok && ws != nil { worksheet := ws.(*xlsxWorksheet) worksheet.Lock() defer worksheet.Unlock() output, _ := xml.Marshal(worksheet) f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) } var colIterator columnXMLIteratorGeneric // do me ! colIterator.cols.sheetXML = f.readBytes(name) decoder := f.xmlNewDecoder(bytes.NewReader(colIterator.cols.sheetXML)) for { token, _ := decoder.Token() if token == nil { break } switch xmlElement := token.(type) { case xml.StartElement: columnXMLHandlerGeneric(&colIterator, &xmlElement) if colIterator.err != nil { return &colIterator.cols, colIterator.err } case xml.EndElement: if xmlElement.Name.Local == "sheetData" { colIterator.cols.f = f colIterator.cols.sheet = trimSheetName(sheet) return &colIterator.cols, nil } } } return &colIterator.cols, nil } // columnXMLHandler parse the column XML element of the worksheet. func columnXMLHandlerGeneric(colIterator *columnXMLIteratorGeneric, xmlElement *xml.StartElement) { colIterator.err = nil inElement := xmlElement.Name.Local if inElement == "row" { colIterator.row++ for _, attr := range xmlElement.Attr { if attr.Name.Local == "r" { if colIterator.curRow, colIterator.err = strconv.Atoi(attr.Value); colIterator.err != nil { return } colIterator.row = colIterator.curRow } } colIterator.cols.totalRows = colIterator.row colIterator.cellCol = 0 } if inElement == "c" { colIterator.cellCol++ for _, attr := range xmlElement.Attr { if attr.Name.Local == "r" { if colIterator.cellCol, _, colIterator.err = CellNameToCoordinates(attr.Value); colIterator.err != nil { return } } } if colIterator.cellCol > colIterator.cols.totalCols { colIterator.cols.totalCols = colIterator.cellCol } } } // columnXMLIterator defined runtime use field for the worksheet column SAX parser. type columnXMLIteratorGeneric struct { err error cols ColsGeneric cellCol, curRow, row int } type ColsGeneric struct { err error curCol, totalCols, totalRows, stashCol int rawCellValue bool sheet string f *File sheetXML []byte sst *xlsxSST } // rowXMLHandler parse the row XML element of the worksheet. func (cols *ColsGeneric) rowXMLHandlerGeneric(rowIterator *rowXMLIteratorGeneric, xmlElement *xml.StartElement, decoder *xml.Decoder) { if rowIterator.inElement == "c" { rowIterator.cellCol++ for _, attr := range xmlElement.Attr { if attr.Name.Local == "r" { if rowIterator.cellCol, rowIterator.cellRow, rowIterator.err = CellNameToCoordinates(attr.Value); rowIterator.err != nil { return } } } blank := rowIterator.cellRow - len(rowIterator.cells) for i := 1; i < blank; i++ { rowIterator.cells = append(rowIterator.cells, "") } if rowIterator.cellCol == cols.curCol { colCell := xlsxC{} _ = decoder.DecodeElement(&colCell, xmlElement) val, _ := colCell.getValueFrom(cols.f, cols.sst, cols.rawCellValue) rowIterator.cells = append(rowIterator.cells, val) } } } func (f *File) GetColsGeneric(sheet string, opts ...Options) ([][]any, error) { cols, err := f.ColsGeneric(sheet) if err != nil { return nil, err } results := make([][]any, 0, 64) for cols.Next() { col, _ := cols.RowsGeneric(opts...) results = append(results, col) } return results, nil } // Next will return true if the next column is found. func (cols *ColsGeneric) Next() bool { cols.curCol++ return cols.curCol <= cols.totalCols } // Error will return an error when the error occurs. func (cols *ColsGeneric) Error() error { return cols.err } // Rows return the current column's row values. func (cols *ColsGeneric) Rows(opts ...Options) ([]any, error) { var rowIterator rowXMLIteratorGeneric if cols.stashCol >= cols.curCol { return rowIterator.cells, rowIterator.err } cols.rawCellValue = parseOptions(opts...).RawCellValue if cols.sst, rowIterator.err = cols.f.sharedStringsReader(); rowIterator.err != nil { return rowIterator.cells, rowIterator.err } decoder := cols.f.xmlNewDecoder(bytes.NewReader(cols.sheetXML)) for { token, _ := decoder.Token() if token == nil { break } switch xmlElement := token.(type) { case xml.StartElement: rowIterator.inElement = xmlElement.Name.Local if rowIterator.inElement == "row" { rowIterator.cellCol = 0 rowIterator.cellRow++ attrR, _ := attrValToInt("r", xmlElement.Attr) if attrR != 0 { rowIterator.cellRow = attrR } } if cols.rowXMLHandlerGeneric(&rowIterator, &xmlElement, decoder); rowIterator.err != nil { return rowIterator.cells, rowIterator.err } case xml.EndElement: if xmlElement.Name.Local == "sheetData" { return rowIterator.cells, rowIterator.err } } } return rowIterator.cells, rowIterator.err } // Functions: rows func (f *File) GetRowsGeneric(sheet string, opts ...Options) ([][]any, error) { rows, err := f.RowsGeneric(sheet) if err != nil { return nil, err } results, cur, max := make([][]any, 0, 64), 0, 0 for rows.Next() { cur++ row, err := rows.ColumnsGeneric(opts...) if err != nil { break } results = append(results, row) if len(row) > 0 { max = cur } } return results[:max], rows.Close() } type RowsGeneric struct { err error curRow, seekRow int needClose, rawCellValue bool sheet string f *File tempFile *os.File sst *xlsxSST decoder *xml.Decoder token xml.Token curRowOpts, seekRowOpts RowOpts } func (f *File) RowsGeneric(sheet string) (*RowsGeneric, error) { name, ok := f.getSheetXMLPath(sheet) if !ok { return nil, ErrSheetNotExist{sheet} } if ws, ok := f.Sheet.Load(name); ok && ws != nil { worksheet := ws.(*xlsxWorksheet) worksheet.Lock() defer worksheet.Unlock() // flush data output, _ := xml.Marshal(worksheet) f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) } var err error rows := RowsGeneric{f: f, sheet: name} rows.needClose, rows.decoder, rows.tempFile, err = f.xmlDecoder(name) return &rows, err } // Next will return true if find the next row element. func (rows *RowsGeneric) Next() bool { rows.seekRow++ if rows.curRow >= rows.seekRow { rows.curRowOpts = rows.seekRowOpts return true } for { token, _ := rows.decoder.Token() if token == nil { return false } switch xmlElement := token.(type) { case xml.StartElement: if xmlElement.Name.Local == "row" { rows.curRow++ if rowNum, _ := attrValToInt("r", xmlElement.Attr); rowNum != 0 { rows.curRow = rowNum } rows.token = token rows.curRowOpts = extractRowOpts(xmlElement.Attr) return true } case xml.EndElement: if xmlElement.Name.Local == "sheetData" { return false } } } } func (rows *RowsGeneric) Close() error { if rows.tempFile != nil { return rows.tempFile.Close() } return nil } // Columns return the current row's column values. This fetches the worksheet // data as a stream, returns each cell in a row as is, and will not skip empty // rows in the tail of the worksheet. func (rows *RowsGeneric) ColumnsGeneric(opts ...Options) ([]any, error) { if rows.curRow > rows.seekRow { return nil, nil } var rowIterator rowXMLIteratorGeneric var token xml.Token rows.rawCellValue = parseOptions(opts...).RawCellValue if rows.sst, rowIterator.err = rows.f.sharedStringsReader(); rowIterator.err != nil { return rowIterator.cells, rowIterator.err } for { if rows.token != nil { token = rows.token } else if token, _ = rows.decoder.Token(); token == nil { break } switch xmlElement := token.(type) { case xml.StartElement: rowIterator.inElement = xmlElement.Name.Local if rowIterator.inElement == "row" { rowNum := 0 if rowNum, rowIterator.err = attrValToInt("r", xmlElement.Attr); rowNum != 0 { rows.curRow = rowNum } 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.cells, rowIterator.err } } if rows.rowXMLHandlerGeneric(&rowIterator, &xmlElement, rows.rawCellValue); rowIterator.err != nil { rows.token = nil return rowIterator.cells, rowIterator.err } rows.token = nil case xml.EndElement: if xmlElement.Name.Local == "sheetData" { return rowIterator.cells, rowIterator.err } } } return rowIterator.cells, rowIterator.err } type rowXMLIteratorGeneric struct { err error inElement string cellCol, cellRow int cells []any } // rowXMLHandler parse the row XML element of the worksheet. func (rows *RowsGeneric) rowXMLHandlerGeneric(rowIterator *rowXMLIteratorGeneric, xmlElement *xml.StartElement, raw bool) { if rowIterator.inElement == "c" { rowIterator.cellCol++ colCell := xlsxC{} _ = rows.decoder.DecodeElement(&colCell, xmlElement) if colCell.R != "" { if rowIterator.cellCol, _, rowIterator.err = CellNameToCoordinates(colCell.R); rowIterator.err != nil { return } } blank := rowIterator.cellCol - len(rowIterator.cells) if val, _ := colCell.getValueFromGeneric(rows.f, rows.sst, raw); val != "" || colCell.F != nil { rowIterator.cells = append(appendSpaceGeneric(blank, rowIterator.cells), val) } } } // appendSpace append blank characters to slice by given length and source slice. // This will append strings to it, then. Not sure why this exists, but hey. func appendSpaceGeneric(l int, s []any) []any{ for i := 1; i < l; i++ { s = append(s, "") } return s } // This should be cell_generic.go but that can be a TODO func (c *xlsxC) getValueFromGeneric(f *File, d *xlsxSST, raw bool) (any, error) { f.Lock() defer f.Unlock() switch c.T { case "b": // done, not tested // fmt.Println("Calling c.getcellBoolGeneric") return c.getCellBoolGeneric(f, raw) case "d": // done, not tested // fmt.Println("Calling c.getCellDateGeneric") return c.getCellDateGeneric(f, raw) case "s": // Don't touch, strings are a-ok if c.V != "" { xlsxSI := 0 xlsxSI, _ = strconv.Atoi(c.V) if _, ok := f.tempFiles.Load(defaultXMLPathSharedStrings); ok { return f.formattedValue(c.S, f.getFromStringItem(xlsxSI), raw) } if len(d.SI) > xlsxSI { return f.formattedValue(c.S, d.SI[xlsxSI].String(), raw) } } return f.formattedValue(c.S, c.V, raw) case "inlineStr": // as are inline strs. So don't touch. if c.IS != nil { return f.formattedValue(c.S, c.IS.String(), raw) } return f.formattedValue(c.S, c.V, raw) default: // For integrals and floatings? These need to go into int or float64. // We need to change it to return an int or a decimal. // fmt.Println("Calling c.getCellNumeric") return c.getCellNumeric(f, raw) // if isNum, precision, decimal := isNumeric(c.V); isNum && !raw { // if precision > 15 { // c.V = strconv.FormatFloat(decimal, 'G', 15, 64) // } else { // c.V = strconv.FormatFloat(decimal, 'f', -1, 64) // } // } // return f.formattedValue(c.S, c.V, raw) } } // NOTE: This function returns any. Now, 99% of the time it should be // bool. But I can't be asked to handle errors, or rewrite formattedValue, and // so it might return a string. Just be warned, future me. func (c *xlsxC) getCellBoolGeneric(f *File, raw bool) (any, error) { if c.V == "1" { return true, nil } if c.V == "0" { return false, nil } return f.formattedValueGeneric(c.S, c.V, raw) } // TODO: I probably actually have to like, do something, to get a valid // date from this. I think Go has a type for dates which gets validly written // and accepted in the writing functions. // Actually, this function is really dirty. It actually sets the c.V value to // the format of Excel time (in float64 form?) and then sends it on its way to // f.formattedValue I think. Which means unless I change formattedValue, this // will return a string, as that fn returns a string. // So then, TODO: Fix formattedValue to return a date value (or just fix this // function not to call formattedValue, and just to return a proper date like // it should. I don't want to return a float64, but a time.Time I believe. func (c *xlsxC) getCellDateGeneric(f *File, raw bool) (time.Time, error) { fmt.Printf("getCellDateGeneric with date %v\n", c.V) // if !raw { layout := "20060102T150405.999" if strings.HasSuffix(c.V, "Z") { layout = "20060102T150405Z" if strings.Contains(c.V, "-") { layout = "2006-01-02T15:04:05Z" } } else if strings.Contains(c.V, "-") { layout = "2006-01-02 15:04:05Z" } if timestamp, err := time.Parse(layout, strings.ReplaceAll(c.V, ",", ".")); err == nil { return timestamp, nil excelTime, _ := timeToExcelTime(timestamp, false) c.V = strconv.FormatFloat(excelTime, 'G', 15, 64) } // else { //errmess := fmt.Sprintf("getCellDateGeneric: failed to parse %s", c.V) //return time.Now(), errors.New(errmess) // } /* This is the error case */ return time.Now(), errors.New("getCellDateGeneric failed") // } // I don't think this vv is needed? // return f.formattedValue(c.S, c.V, raw) } // For now, we could just try parse as a float, and if it doesn't work parse as // an int, and if it doesn't work cry. func (c *xlsxC) getCellNumeric(f *File, raw bool) (any, error) { // fmt.Printf("rows.getCellNumeric with value %v\n", c.V) // This can either be a float or an int. If it is a float, return a // float64. Otherwise, return an int. /* TODO: implement according to above comment. */ if isNum, precision, decimal := isNumeric(c.V); isNum && !raw { // fmt.Printf("isNum true, prec{%v} ; decimal(%v)\n", precision, decimal) /* new way, see comment below */ /* 2023-02-28: I'm commenting this out to try have it call * formattedValueGeneric, which should (?) solve my worries. */ // v, err := strconv.ParseInt(c.V, 10, 64) // if err != nil { // return strconv.ParseFloat(c.V, 64) // } // return v, err if precision > 15 { // We actually just need a way to return a float. I // say try return int first, if it doesn't work try // float, else cry and die. // Return here a float64 // r, err := strconv.ParseFloat(c.V, 64) // fmt.Printf("prec15: ret(%v)\n", r) // return r, err c.V = strconv.FormatFloat(decimal, 'G', 15, 64) } else { // I think, return here an int? // return strconv.ParseInt(c.V, 10, 64) c.V = strconv.FormatFloat(decimal, 'f', -1, 64) } } // fmt.Printf("getCellNumeric is failing, value %v\n", c.V) // return nil, errors.New("getCellNumeric failed like") return f.formattedValueGeneric(c.S, c.V, raw) }