Repository: qax-os/excelize

Issue:
Flexability in temporary folder in StreamWriter files
The StreamWriter feature in Excelize is great for handling large XLSX file generation. Currently, temporary files are created using Golang's os.CreateTemp, as seen in [stream.go#L740](https://github.com/qax-os/excelize/blob/30d3561d0e9bceb3a669ee864e5dcc672eff5341/stream.go#L740).

In some cases, relying on os.CreateTemp is not flexible enough. Specifically, there are scenarios where it's preferable to direct these temporary files to different or dynamically determined storage locations based on the request context. This restriction can limit the usage of StreamWriter in more complex environments.

I propose adding an option in StreamWriter to allow users to provide a custom temporary file creation method. This would follow the options pattern, maintaining the current behavior as the default. The user would be able to specify their own TempFileFactory, allowing for dynamic control over the temporary file path.

For example:
```golang
type TempFileFactory func() (*os.File, error)

var DefaultTempFileFactory TempFileFactory = func() {
    return os.CreateTemp(os.TempDir(), "excelize-")
}


// Line 740:
bw.tmp, err = newTempFile();
```

I have no problem to contribue this solution for the library if there is an agreement on a solution.

Discussion:
@xuri: Thanks for your suggestion. The temporary file not only used in stream writer, but also in stream reader ([rows iterator](https://github.com/xuri/excelize/blob/30d3561d0e9bceb3a669ee864e5dcc672eff5341/rows.go#L312)), you can set `TMPDIR` environment variable for excelize application in the runtime to changed temporary file location, does this make sense? If we decide expose this for the user, add new fields `TempDir` in the Options data type would be better.@eliyaoo32: Using the `TMPDIR` environment makes sense if it's fine to have all the same temporary file under the same folder.
But sometimes there are needs to put the temporary files under different places for different context.
For example, use different mount/harddisk for different clients or utillizing different disks based on their free-space at the moment.
Management of this logic is impossible throguh env variable since the variable is shared between threads.

Adding a new field TempDir as optional data is a great option as well. (Less flexable but easier)@xuri: I suggest check the optional value `TempDir` if exist when open or create workbook, contribution are welcome.@eliyaoo32: Sure, I'll work on a contribution for it.@xuri: The new field `TempDir` has been added to the `Options` data type. Please upgrade the master branch code by `go get -u github.com/xuri/excelize/v2@master`, and this feature will be released in the next version.

Solution patch:
diff --git a/excelize.go b/excelize.go
index 52b7fa3..b8f2f37 100644
--- a/excelize.go
+++ b/excelize.go
@@ -98,12 +98,16 @@ type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, e
 //
 // CultureInfo specifies the country code for applying built-in language number
 // format code these effect by the system's local language settings.
+//
+// TmpDir specifies the temporary directory for creating temporary files, if the
+// value is empty, the system default temporary directory will be used.
 type Options struct {
 	MaxCalcIterations uint
 	Password          string
 	RawCellValue      bool
 	UnzipSizeLimit    int64
 	UnzipXMLSizeLimit int64
+	TmpDir            string
 	ShortDatePattern  string
 	LongDatePattern   string
 	LongTimePattern   string
diff --git a/lib.go b/lib.go
index 7eeb813..7c5cdfd 100644
--- a/lib.go
+++ b/lib.go
@@ -80,7 +80,7 @@ func (f *File) ReadZipReader(r *zip.Reader) (map[string][]byte, int, error) {
 // unzipToTemp unzip the zip entity to the system temporary directory and
 // returned the unzipped file path.
 func (f *File) unzipToTemp(zipFile *zip.File) (string, error) {
-	tmp, err := os.CreateTemp("", "excelize-")
+	tmp, err := os.CreateTemp(f.options.TmpDir, "excelize-")
 	if err != nil {
 		return "", err
 	}
diff --git a/rows.go b/rows.go
index 66485e6..4239cc0 100644
--- a/rows.go
+++ b/rows.go
@@ -371,7 +371,7 @@ func (f *File) getFromStringItem(index int) string {
 		}()
 	}
 	f.sharedStringItem = [][]uint{}
-	f.sharedStringTemp, _ = os.CreateTemp("", "excelize-")
+	f.sharedStringTemp, _ = os.CreateTemp(f.options.TmpDir, "excelize-")
 	f.tempFiles.Store(defaultTempFileSST, f.sharedStringTemp.Name())
 	var (
 		inElement string
diff --git a/stream.go b/stream.go
index 63309ff..e4844b6 100644
--- a/stream.go
+++ b/stream.go
@@ -123,6 +123,7 @@ func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
 		file:    f,
 		Sheet:   sheet,
 		SheetID: sheetID,
+		rawData: bufferedWriter{tmpDir: f.options.TmpDir},
 	}
 	var err error
 	sw.worksheet, err = f.workSheetReader(sheet)
@@ -737,8 +738,9 @@ func bulkAppendFields(w io.Writer, ws *xlsxWorksheet, from, to int) {
 // is written to the temp file with Sync, which may return an error.
 // Therefore, Sync should be periodically called and the error checked.
 type bufferedWriter struct {
-	tmp *os.File
-	buf bytes.Buffer
+	tmpDir string
+	tmp    *os.File
+	buf    bytes.Buffer
 }
 
 // Write to the in-memory buffer. The error is always nil.
@@ -775,7 +777,7 @@ func (bw *bufferedWriter) Sync() (err error) {
 		return nil
 	}
 	if bw.tmp == nil {
-		bw.tmp, err = os.CreateTemp("", "excelize-")
+		bw.tmp, err = os.CreateTemp(bw.tmpDir, "excelize-")
 		if err != nil {
 			// can not use local storage
 			return nil



Test patch:
diff --git a/stream_test.go b/stream_test.go
index a2eea18..1fa480a 100644
--- a/stream_test.go
+++ b/stream_test.go
@@ -97,7 +97,7 @@ func TestStreamWriter(t *testing.T) {
 	assert.NoError(t, file.Close())
 
 	// Test close temporary file error
-	file = NewFile()
+	file = NewFile(Options{TmpDir: os.TempDir()})
 	streamWriter, err = file.NewStreamWriter("Sheet1")
 	assert.NoError(t, err)
 	for rowID := 10; rowID <= 25600; rowID++ {