# 把 params.csv 生成一份真正的 .xlsx (最小 OOXML 结构, 免依赖 Excel) # 用法: powershell -NoProfile -ExecutionPolicy Bypass -File tools\gen-xlsx.ps1 # 说明: 仅供查看格式参考; Builder 导入用 CSV (Excel 里另存为 CSV 即可)。 param( [string]$Csv = (Join-Path $PSScriptRoot '..\onb-sc\params.csv'), [string]$Out = (Join-Path $PSScriptRoot '..\onb-sc\params.xlsx') ) Add-Type -AssemblyName System.IO.Compression Add-Type -AssemblyName System.IO.Compression.FileSystem function Xml-Esc([string]$s) { return $s.Replace('&', '&').Replace('<', '<').Replace('>', '>').Replace('"', '"') } # 列号 → 列字母 (A..Z, AA..) function Col-Name([int]$i) { $name = '' $n = $i + 1 while ($n -gt 0) { $r = ($n - 1) % 26 $name = [char](65 + $r) + $name $n = [int](($n - 1) / 26) } return $name } $rows = Get-Content $Csv | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } if ($rows.Count -lt 1) { Write-Output "CSV 为空"; exit 1 } # ---- sheet1.xml ---- $sb = New-Object System.Text.StringBuilder [void]$sb.Append('') [void]$sb.Append('') for ($r = 0; $r -lt $rows.Count; $r++) { $cells = $rows[$r].Split(',') [void]$sb.Append('') for ($c = 0; $c -lt $cells.Count; $c++) { $val = $cells[$c] $ref = (Col-Name $c) + ($r + 1) $num = 0.0 if ($r -gt 0 -and [double]::TryParse($val, [System.Globalization.NumberStyles]::Float, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$num)) { [void]$sb.Append('' + $val + '') } else { [void]$sb.Append('' + (Xml-Esc $val) + '') } } [void]$sb.Append('') } [void]$sb.Append('') $sheetXml = $sb.ToString() # ---- 其余固定部件 ---- $contentTypes = '' + '' + '' + '' + '' + '' + '' $rels = '' + '' + '' + '' $workbook = '' + '' + '' $workbookRels = '' + '' + '' + '' $enc = New-Object System.Text.UTF8Encoding($false) # 手动 CreateEntry 控制条目名 (OOXML 规范要求正斜杠, CreateFromDirectory 会生成反斜杠) if (Test-Path $Out) { Remove-Item $Out -Force } $zip = [System.IO.Compression.ZipFile]::Open($Out, [System.IO.Compression.ZipArchiveMode]::Create) function Add-Entry([string]$name, [string]$content) { $e = $zip.CreateEntry($name) $sw = New-Object System.IO.StreamWriter($e.Open(), $enc) $sw.Write($content) $sw.Close() } Add-Entry '[Content_Types].xml' $contentTypes Add-Entry '_rels/.rels' $rels Add-Entry 'xl/workbook.xml' $workbook Add-Entry 'xl/_rels/workbook.xml.rels' $workbookRels Add-Entry 'xl/worksheets/sheet1.xml' $sheetXml $zip.Dispose() # ---- 自检: 重新打开并校验 XML 结构 ---- $z = [System.IO.Compression.ZipFile]::OpenRead($Out) $ok = $true foreach ($part in @('[Content_Types].xml', '_rels/.rels', 'xl/workbook.xml', 'xl/_rels/workbook.xml.rels', 'xl/worksheets/sheet1.xml')) { try { $e = $z.GetEntry($part) $sr = New-Object System.IO.StreamReader($e.Open()) $null = [xml]$sr.ReadToEnd() $sr.Close() } catch { Write-Output ("XML 无效: " + $part); $ok = $false } } $z.Dispose() if ($ok) { Write-Output ("xlsx 生成并通过自检: " + $Out + " (" + $rows.Count + " 行)") }