Skip to content

streaming writer set default value like non-streaming one #2152

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,10 @@ func (c *xlsxC) getCellBool(f *File, raw bool) (string, error) {
return f.formattedValue(c, raw, CellTypeBool)
}

// SetCellDefaultValue is a type for setCellDefault function, pass this type data, setCellDefault will be used to
// perform the write operation.
type SetCellDefaultValue string

// setCellDefault prepares cell type and string type cell value by a given
// string.
func (c *xlsxC) setCellDefault(value string) {
Expand All @@ -580,7 +584,8 @@ func (c *xlsxC) setCellDefault(value string) {
c.IS.T.Val = value
return
}
c.T, c.V, c.IS = value, value, nil

c.T, c.V, c.IS = "", "", nil // I modify this to set c.T = "" explicitly to avoid misunderstanding of c.Type can be value
return
}
c.T, c.V = "", value
Expand Down
20 changes: 10 additions & 10 deletions rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,31 +247,31 @@ func (rows *Rows) rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.Sta
}

// cellXMLAttrHandler parse the cell XML element attributes of the worksheet.
func (cell *xlsxC) cellXMLAttrHandler(start *xml.StartElement) error {
func (c *xlsxC) cellXMLAttrHandler(start *xml.StartElement) error {
for _, attr := range start.Attr {
switch attr.Name.Local {
case "r":
cell.R = attr.Value
c.R = attr.Value
case "s":
val, err := strconv.ParseInt(attr.Value, 10, 64)
if err != nil {
return err
}
if math.MinInt <= val && val <= math.MaxInt {
cell.S = int(val)
c.S = int(val)
}
case "t":
cell.T = attr.Value
c.T = attr.Value
default:
}
}
return nil
}

// cellXMLHandler parse the cell XML element of the worksheet.
func (cell *xlsxC) cellXMLHandler(decoder *xml.Decoder, start *xml.StartElement) error {
cell.XMLName = start.Name
err := cell.cellXMLAttrHandler(start)
func (c *xlsxC) cellXMLHandler(decoder *xml.Decoder, start *xml.StartElement) error {
c.XMLName = start.Name
err := c.cellXMLAttrHandler(start)
if err != nil {
return err
}
Expand All @@ -286,11 +286,11 @@ func (cell *xlsxC) cellXMLHandler(decoder *xml.Decoder, start *xml.StartElement)
se = el
switch se.Name.Local {
case "v":
err = decoder.DecodeElement(&cell.V, &se)
err = decoder.DecodeElement(&c.V, &se)
case "f":
err = decoder.DecodeElement(&cell.F, &se)
err = decoder.DecodeElement(&c.F, &se)
case "is":
err = decoder.DecodeElement(&cell.IS, &se)
err = decoder.DecodeElement(&c.IS, &se)
}
if err != nil {
return err
Expand Down
2 changes: 2 additions & 0 deletions stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,8 @@ func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error {
case []RichTextRun:
c.T, c.IS = "inlineStr", &xlsxSI{}
c.IS.R, err = setRichText(val)
case SetCellDefaultValue:
c.setCellDefault(string(val))
default:
c.setCellValue(fmt.Sprint(val))
}
Expand Down
12 changes: 10 additions & 2 deletions stream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,14 @@ func TestStreamWriter(t *testing.T) {
assert.Equal(t, ErrMaxRowHeight, streamWriter.SetRow("A8", nil, RowOpts{Height: MaxRowHeight + 1}))

assert.NoError(t, streamWriter.SetRow("A9", []interface{}{math.NaN(), math.Inf(0), math.Inf(-1)}))
assert.NoError(t, streamWriter.SetRow("A10", []interface{}{
SetCellDefaultValue("1.0"), "1.0", 1.0,
}))
assert.NoError(t, streamWriter.SetRow("A11", []interface{}{
SetCellDefaultValue("2.0"), "2.0", 2.0,
}))

for rowID := 10; rowID <= 51200; rowID++ {
for rowID := 12; rowID <= 51200; rowID++ {
row := make([]interface{}, 50)
for colID := 0; colID < 50; colID++ {
row[colID] = rand.Intn(640000)
Expand Down Expand Up @@ -148,7 +154,7 @@ func TestStreamWriter(t *testing.T) {
cells += len(row)
}
assert.NoError(t, rows.Close())
assert.Equal(t, 2559562, cells)
assert.Equal(t, 2559468, cells)
// Save spreadsheet with password.
assert.NoError(t, file.SaveAs(filepath.Join("test", "EncryptionTestStreamWriter.xlsx"), Options{Password: "password"}))
assert.NoError(t, file.Close())
Expand Down Expand Up @@ -408,6 +414,8 @@ func TestStreamSetCellValFunc(t *testing.T) {
true,
nil,
complex64(5 + 10i),
SetCellDefaultValue("100.1588"),
SetCellDefaultValue(" Hello"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If set specific characters, it will caused corrupted workbook. For example:

err := sw.SetRow("A1", []interface{}{excelize.SetCellDefaultValue("<>"))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

func (c *xlsxC) setCellDefault(value string) {
	if ok, _, _ := isNumeric(value); !ok {
		if value != "" {
			c.setInlineStr(value)
			c.IS.T.Val = value
			return
		}

		c.T, c.V, c.IS = "", "", nil // I modify this to set c.T = "" explicitly to avoid misunderstanding of c.Type can be value
		return
	}
	c.T, c.V = "", value
}


I think the difference between setCellDefault and setCellString is the former can set c.T to a empty string, i didn't find the path to do that using streaming writer. 

Copy link
Member

@xuri xuri Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cell type is empty only for no-numeric cell values, here an example for different data type of set cell values with SetCellDefault and stream mode function:

package main

import (
    "fmt"

    "github.com/xuri/excelize/v2"
)

func main() {
    f := excelize.NewFile()
    defer func() {
        if err := f.Close(); err != nil {
            fmt.Println(err)
        }
    }()
    if err := f.SetCellDefault("Sheet1", "A1", "100.1588"); err != nil {
        fmt.Println(err)
        return
    }
    if err := f.SetCellDefault("Sheet1", "B1", "<>"); err != nil {
        fmt.Println(err)
        return
    }

    _, err := f.NewSheet("Sheet2")
    if err != nil {
        fmt.Println(err)
        return
    }
    sw, err := f.NewStreamWriter("Sheet2")
    if err != nil {
        fmt.Println(err)
        return
    }
    if err := sw.SetRow("A1", []interface{}{100.1588, "<>"}); err != nil {
        fmt.Println(err)
        return
    }
    if err := sw.Flush(); err != nil {
        fmt.Println(err)
        return
    }
    if err := f.SaveAs("Book1.xlsx"); err != nil {
        fmt.Println(err)
    }
}

the generated XML element of Sheet1!A1 is same as Sheet2!A1, and Sheet2!B1 is same as Sheet2!B1:

<row r="1">
    <c r="A1">
        <v>100.1588</v>
    </c>
    <c r="B1" t="inlineStr">
        <is>
            <t>&lt;&gt;</t>
        </is>
    </c>
</row>

So you can using stream writer to generats same things that same as SetCellDefault function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Got it, you are right. I will check it again.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've closed this, if you have any questions, please let me know, and I can reopen this anytime.

} {
assert.NoError(t, sw.setCellValFunc(c, val))
}
Expand Down