Files
story/scripts/extract-requirements.ps1
T

122 lines
4.7 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
param(
[Parameter(Mandatory = $true)]
[string]$Source,
[string]$Output = (Join-Path $PSScriptRoot '..\artifacts\requirements-source-inventory.md')
)
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.IO.Compression.FileSystem
$sourcePath = (Resolve-Path -LiteralPath $Source).Path
$outputPath = [System.IO.Path]::GetFullPath($Output)
$outputDirectory = Split-Path -Parent $outputPath
if (-not (Test-Path -LiteralPath $outputDirectory)) {
New-Item -ItemType Directory -Path $outputDirectory | Out-Null
}
$stylesByID = @{}
$archive = [System.IO.Compression.ZipFile]::OpenRead($sourcePath)
try {
$entry = $archive.GetEntry('word/document.xml')
if ($null -eq $entry) {
throw 'DOCX中缺少word/document.xml'
}
$stream = $entry.Open()
try {
$reader = [System.IO.StreamReader]::new($stream, [System.Text.Encoding]::UTF8, $true)
try {
[xml]$document = $reader.ReadToEnd()
}
finally {
$reader.Dispose()
}
}
finally {
$stream.Dispose()
}
$stylesEntry = $archive.GetEntry('word/styles.xml')
if ($null -ne $stylesEntry) {
$stylesStream = $stylesEntry.Open()
try {
$stylesReader = [System.IO.StreamReader]::new($stylesStream, [System.Text.Encoding]::UTF8, $true)
try {
[xml]$stylesDocument = $stylesReader.ReadToEnd()
}
finally {
$stylesReader.Dispose()
}
}
finally {
$stylesStream.Dispose()
}
$stylesNamespace = [System.Xml.XmlNamespaceManager]::new($stylesDocument.NameTable)
$stylesNamespace.AddNamespace('w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main')
foreach ($styleNode in $stylesDocument.SelectNodes('/w:styles/w:style', $stylesNamespace)) {
$idAttribute = $styleNode.Attributes.GetNamedItem('styleId', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main')
$nameNode = $styleNode.SelectSingleNode('./w:name', $stylesNamespace)
if ($null -eq $idAttribute -or $null -eq $nameNode) {
continue
}
$nameAttribute = $nameNode.Attributes.GetNamedItem('val', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main')
if ($null -ne $nameAttribute) {
$stylesByID[$idAttribute.Value] = $nameAttribute.Value
}
}
}
}
finally {
$archive.Dispose()
}
$namespace = [System.Xml.XmlNamespaceManager]::new($document.NameTable)
$namespace.AddNamespace('w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main')
$paragraphs = $document.SelectNodes('/w:document/w:body/w:p', $namespace)
if ($paragraphs.Count -eq 0) {
throw 'DOCX正文没有可追踪段落'
}
$hash = (Get-FileHash -LiteralPath $sourcePath -Algorithm SHA256).Hash.ToLowerInvariant()
$lines = [System.Collections.Generic.List[string]]::new()
$lines.Add('# 原始需求来源段落清单')
$lines.Add('')
$lines.Add('> 此文件由`scripts/extract-requirements.ps1`从原始DOCX机械提取,用于稳定引用来源,不替代原需求文档。`Pxxxx`是`word/document.xml`正文段落的零基顺序号;空段落保留编号但不输出正文。')
$lines.Add('')
$lines.Add("- 来源文件:$([System.IO.Path]::GetFileName($sourcePath))")
$lines.Add(('- SHA-256:`{0}`' -f $hash))
$lines.Add("- 正文段落总数:$($paragraphs.Count)")
$lines.Add('')
$lines.Add('## 非空段落')
$lines.Add('')
$nonEmpty = 0
for ($index = 0; $index -lt $paragraphs.Count; $index++) {
$paragraph = $paragraphs[$index]
$textParts = $paragraph.SelectNodes('.//w:t', $namespace) | ForEach-Object { $_.InnerText }
$text = (($textParts -join '') -replace '[\r\n\t]+', ' ' -replace '\s{2,}', ' ').Trim()
if ([string]::IsNullOrWhiteSpace($text)) {
continue
}
$nonEmpty++
$styleNode = $paragraph.SelectSingleNode('./w:pPr/w:pStyle', $namespace)
$style = 'Normal'
if ($null -ne $styleNode) {
$styleAttribute = $styleNode.Attributes.GetNamedItem('val', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main')
if ($null -ne $styleAttribute -and -not [string]::IsNullOrWhiteSpace($styleAttribute.Value)) {
$style = $styleAttribute.Value
if ($stylesByID.ContainsKey($style)) {
$style = $stylesByID[$style]
}
}
}
$safeText = $text.Replace('\', '\\').Replace('`', '\`')
$lines.Add(('- `P{0}` [{1}] {2}' -f $index.ToString('0000'), $style, $safeText))
}
$lines.Insert(7, "- 非空段落数:$nonEmpty")
[System.IO.File]::WriteAllLines($outputPath, $lines, [System.Text.UTF8Encoding]::new($false))
Write-Output "已提取 $nonEmpty/$($paragraphs.Count) 个段落:$outputPath"