docs: add development plan and technical design

This commit is contained in:
meswork764
2026-05-27 18:36:20 +08:00
parent acca29b36b
commit 6ecd0b5bd5
9 changed files with 4464 additions and 2 deletions

View File

@@ -0,0 +1,206 @@
param(
[Parameter(Mandatory = $true)]
[string]$PlanMarkdown,
[Parameter(Mandatory = $true)]
[string]$SourceMarkdown,
[Parameter(Mandatory = $true)]
[string]$OutputDocx
)
$ErrorActionPreference = 'Stop'
function Escape-XmlText {
param([AllowNull()][string]$Text)
if ($null -eq $Text) { return '' }
return [System.Security.SecurityElement]::Escape($Text)
}
function Add-Paragraph {
param(
[System.Collections.Generic.List[string]]$Parts,
[string]$Text,
[string]$Style = $null
)
$styleXml = ''
if ($Style) {
$styleXml = "<w:pPr><w:pStyle w:val=`"$Style`"/></w:pPr>"
}
$Parts.Add("<w:p>$styleXml<w:r><w:t xml:space=`"preserve`">$(Escape-XmlText $Text)</w:t></w:r></w:p>")
}
function Add-Table {
param(
[System.Collections.Generic.List[string]]$Parts,
[string[]]$Headers,
[object[]]$Rows
)
$tbl = New-Object System.Text.StringBuilder
[void]$tbl.Append('<w:tbl>')
[void]$tbl.Append('<w:tblPr><w:tblStyle w:val="TableGrid"/><w:tblW w:w="0" w:type="auto"/><w:tblBorders><w:top w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:left w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:bottom w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:right w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:insideH w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:insideV w:val="single" w:sz="4" w:space="0" w:color="auto"/></w:tblBorders></w:tblPr>')
[void]$tbl.Append('<w:tr>')
foreach ($h in $Headers) {
[void]$tbl.Append('<w:tc><w:tcPr><w:shd w:val="clear" w:color="auto" w:fill="D9EAF7"/></w:tcPr><w:p><w:r><w:rPr><w:b/></w:rPr><w:t xml:space="preserve">')
[void]$tbl.Append((Escape-XmlText $h))
[void]$tbl.Append('</w:t></w:r></w:p></w:tc>')
}
[void]$tbl.Append('</w:tr>')
foreach ($row in $Rows) {
[void]$tbl.Append('<w:tr>')
foreach ($cell in $row) {
[void]$tbl.Append('<w:tc><w:p><w:r><w:t xml:space="preserve">')
[void]$tbl.Append((Escape-XmlText ([string]$cell)))
[void]$tbl.Append('</w:t></w:r></w:p></w:tc>')
}
[void]$tbl.Append('</w:tr>')
}
[void]$tbl.Append('</w:tbl>')
$Parts.Add($tbl.ToString())
}
function Flush-TableBuffer {
param(
[System.Collections.Generic.List[string]]$Parts,
[System.Collections.Generic.List[string]]$Buffer
)
if ($Buffer.Count -lt 2) {
foreach ($line in $Buffer) {
Add-Paragraph -Parts $Parts -Text $line
}
$Buffer.Clear()
return
}
$header = $Buffer[0].Trim('|').Split('|') | ForEach-Object { $_.Trim() }
$rows = @()
for ($i = 2; $i -lt $Buffer.Count; $i++) {
$rows += ,($Buffer[$i].Trim('|').Split('|') | ForEach-Object { $_.Trim() })
}
Add-Table -Parts $Parts -Headers $header -Rows $rows
$Buffer.Clear()
}
function Add-MarkdownContent {
param(
[System.Collections.Generic.List[string]]$Parts,
[string]$MarkdownPath
)
$lines = [System.IO.File]::ReadAllLines($MarkdownPath, [System.Text.Encoding]::UTF8)
$tableBuffer = New-Object System.Collections.Generic.List[string]
$inCode = $false
foreach ($line in $lines) {
if ($line.TrimStart().StartsWith('```')) {
if ($tableBuffer.Count -gt 0) { Flush-TableBuffer -Parts $Parts -Buffer $tableBuffer }
$inCode = -not $inCode
Add-Paragraph -Parts $Parts -Text $line -Style 'Code'
continue
}
if ($inCode) {
Add-Paragraph -Parts $Parts -Text $line -Style 'Code'
continue
}
if ($line.TrimStart().StartsWith('|') -and $line.TrimEnd().EndsWith('|')) {
$tableBuffer.Add($line)
continue
}
if ($tableBuffer.Count -gt 0) { Flush-TableBuffer -Parts $Parts -Buffer $tableBuffer }
if ($line.StartsWith('### ')) {
Add-Paragraph -Parts $Parts -Text $line.Substring(4) -Style 'Heading3'
} elseif ($line.StartsWith('## ')) {
Add-Paragraph -Parts $Parts -Text $line.Substring(3) -Style 'Heading2'
} elseif ($line.StartsWith('# ')) {
Add-Paragraph -Parts $Parts -Text $line.Substring(2) -Style 'Heading1'
} elseif ($line.Trim().StartsWith('- ')) {
Add-Paragraph -Parts $Parts -Text ('· ' + $line.Trim().Substring(2))
} elseif ([string]::IsNullOrWhiteSpace($line)) {
Add-Paragraph -Parts $Parts -Text ''
} else {
Add-Paragraph -Parts $Parts -Text $line
}
}
if ($tableBuffer.Count -gt 0) { Flush-TableBuffer -Parts $Parts -Buffer $tableBuffer }
}
if (-not (Test-Path -LiteralPath $PlanMarkdown)) { throw "Plan markdown not found: $PlanMarkdown" }
if (-not (Test-Path -LiteralPath $SourceMarkdown)) { throw "Source markdown not found: $SourceMarkdown" }
$outputDir = Split-Path -Path $OutputDocx -Parent
if (-not (Test-Path -LiteralPath $outputDir)) {
New-Item -ItemType Directory -Path $outputDir | Out-Null
}
$tempRoot = Join-Path $env:TEMP ("docx_" + [Guid]::NewGuid().ToString('N'))
$wordDir = Join-Path $tempRoot 'word'
$relsDir = Join-Path $tempRoot '_rels'
$wordRelsDir = Join-Path $wordDir '_rels'
New-Item -ItemType Directory -Path $wordDir, $relsDir, $wordRelsDir | Out-Null
$parts = New-Object System.Collections.Generic.List[string]
Add-MarkdownContent -Parts $parts -MarkdownPath $PlanMarkdown
Add-MarkdownContent -Parts $parts -MarkdownPath $SourceMarkdown
$documentXml = @"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
$($parts -join "`n")
<w:sectPr>
<w:pgSz w:w="11906" w:h="16838"/>
<w:pgMar w:top="1440" w:right="1080" w:bottom="1440" w:left="1080" w:header="720" w:footer="720" w:gutter="0"/>
</w:sectPr>
</w:body>
</w:document>
"@
$stylesXml = @'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:style w:type="paragraph" w:styleId="Normal"><w:name w:val="Normal"/><w:qFormat/><w:rPr><w:rFonts w:ascii="Microsoft YaHei" w:eastAsia="Microsoft YaHei" w:hAnsi="Microsoft YaHei"/><w:sz w:val="21"/></w:rPr></w:style>
<w:style w:type="paragraph" w:styleId="Heading1"><w:name w:val="heading 1"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:spacing w:before="360" w:after="120"/><w:outlineLvl w:val="0"/></w:pPr><w:rPr><w:rFonts w:ascii="Microsoft YaHei" w:eastAsia="Microsoft YaHei" w:hAnsi="Microsoft YaHei"/><w:b/><w:sz w:val="30"/></w:rPr></w:style>
<w:style w:type="paragraph" w:styleId="Heading2"><w:name w:val="heading 2"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:spacing w:before="280" w:after="100"/><w:outlineLvl w:val="1"/></w:pPr><w:rPr><w:rFonts w:ascii="Microsoft YaHei" w:eastAsia="Microsoft YaHei" w:hAnsi="Microsoft YaHei"/><w:b/><w:sz w:val="26"/></w:rPr></w:style>
<w:style w:type="paragraph" w:styleId="Heading3"><w:name w:val="heading 3"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:spacing w:before="220" w:after="80"/><w:outlineLvl w:val="2"/></w:pPr><w:rPr><w:rFonts w:ascii="Microsoft YaHei" w:eastAsia="Microsoft YaHei" w:hAnsi="Microsoft YaHei"/><w:b/><w:sz w:val="23"/></w:rPr></w:style>
<w:style w:type="paragraph" w:styleId="Code"><w:name w:val="Code"/><w:basedOn w:val="Normal"/><w:qFormat/><w:rPr><w:rFonts w:ascii="Consolas" w:eastAsia="Microsoft YaHei" w:hAnsi="Consolas"/><w:sz w:val="18"/></w:rPr></w:style>
<w:style w:type="table" w:styleId="TableGrid"><w:name w:val="Table Grid"/><w:basedOn w:val="TableNormal"/><w:uiPriority w:val="59"/><w:tblPr><w:tblBorders><w:top w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:left w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:bottom w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:right w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:insideH w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:insideV w:val="single" w:sz="4" w:space="0" w:color="auto"/></w:tblBorders></w:tblPr></w:style>
</w:styles>
'@
$contentTypesXml = @'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
<Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>
</Types>
'@
$relsXml = @'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>
'@
$docRelsXml = @'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
</Relationships>
'@
[System.IO.File]::WriteAllText((Join-Path $tempRoot '[Content_Types].xml'), $contentTypesXml, [System.Text.Encoding]::UTF8)
[System.IO.File]::WriteAllText((Join-Path $relsDir '.rels'), $relsXml, [System.Text.Encoding]::UTF8)
[System.IO.File]::WriteAllText((Join-Path $wordDir 'document.xml'), $documentXml, [System.Text.Encoding]::UTF8)
[System.IO.File]::WriteAllText((Join-Path $wordDir 'styles.xml'), $stylesXml, [System.Text.Encoding]::UTF8)
[System.IO.File]::WriteAllText((Join-Path $wordRelsDir 'document.xml.rels'), $docRelsXml, [System.Text.Encoding]::UTF8)
if (Test-Path -LiteralPath $OutputDocx) {
Remove-Item -LiteralPath $OutputDocx -Force
}
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::CreateFromDirectory($tempRoot, $OutputDocx)
Remove-Item -LiteralPath $tempRoot -Recurse -Force
Write-Output $OutputDocx