[PowerShell] Word를 사용하지 않고 Docx 파일에서 텍스트/댓글/굵게/표기 정보를 꺼냅니다(2021년 버전)
57221 단어 PowerShellWordtech
환경:
> $PSVersionTable
Name Value
---- -----
PSVersion 7.1.4
PSEdition Core
GitCommitId 7.1.4
OS Microsoft Windows 10.0.19042
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
클래스 만들기
Docx 내의 Xml를 읽기 위해 성형된 클래스입니다.
각 방법의 해설은 잠시 후에 진행한다.
if ("System.IO.Compression.Filesystem" -notin ([System.AppDomain]::CurrentDomain.GetAssemblies() | ForEach-Object{$_.GetName().Name})) {
Add-Type -AssemblyName System.IO.Compression.Filesystem
}
class Docx {
static [bool] IsOpened ([string]$path) {
$stream = $null
$inAccessible = $false
try {
$stream = [System.IO.FileStream]::new($path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
}
catch {
$inAccessible = $true
}
finally {
if($stream) {
$stream.Close()
}
}
return $inAccessible
}
static [string] Cat ([string]$path, [string]$relPath) {
$content = ""
$archive = [IO.Compression.Zipfile]::OpenRead($path)
$entry = $archive.GetEntry($relPath)
if ($entry) {
$stream = $entry.Open()
$reader = New-Object IO.StreamReader($stream)
$content = $reader.ReadToEnd()
$reader.Close()
$stream.Close()
}
$archive.Dispose()
return $content
}
static [string] RemoveNoise ([string]$s) {
return $s.Replace("<w:t xml:space=`"preserve`">", "<w:t>") -replace "<mc:FallBack>.+?</mc:FallBack>"
}
static [PSCustomObject] GetXml ([string]$path) {
if ([Docx]::IsOpened($path)) {
return [PSCustomObject]@{
"Status" = "FILEOPENED";
"Xml" = "";
}
}
$content = [docx]::Cat($path, "word/document.xml")
return [PSCustomObject]@{
"Status" = "OK";
"Xml" = [Docx]::RemoveNoise($content);
}
}
static [PSCustomObject] GetCommentsMarkup ([string]$path) {
if ([Docx]::IsOpened($path)) {
return [PSCustomObject]@{
"Status" = "FILEOPENED";
"Xml" = "";
}
}
$content = [Docx]::Cat($path, "word/comments.xml")
return [PSCustomObject]@{
"Status" = ($content)? "OK" : "NOCOMMENT";
"Xml" = $content;
}
}
static [string[]] GetParagraphs ([string]$content) {
$m = [regex]::Matches($content, "<w:p [^>]+?>.+?</w:p>")
return $m.Value
}
static [string[]] GetRanges ([string]$content) {
$m = [regex]::Matches($content, "(<w:r>.+?</w:r>)|(<w:r w:[^>]+?>.+?</w:r>)")
return $m.Value
}
static [string[]] GetComments ([string]$content) {
$m = [regex]::Matches($content, "<w:comment w:[^>]*?>.*?</w:comment>")
return $m.Value
}
static [string] GetText ([string]$nodeContent) {
$m = [regex]::Matches($nodeContent, "(?<=<w:t>).+?(?=</w:t>)")
return ($m.Value -replace "&", "&") -join ""
}
static [string[]] FilterNode ([string[]]$nodes, [string]$pattern) {
$arr = New-Object System.Collections.ArrayList
$sb = New-Object System.Text.StringBuilder
foreach ($n in $nodes) {
if ($n -match $pattern) {
$sb.Append($n) > $null
}
else {
$s = $sb.ToString()
if ($sb.Length) {
$arr.Add($s) > $null
}
$sb.Clear()
}
}
return $arr
}
}
정적 방법의 조합은 반의 본래 장점을 발휘할 수 없을 것 같지만 용서해 주십시오...(상세한 사람들의 조언을 기대합니다!)상세한 해설
사전 준비
Docx 파일의 실제 상태는 zip이기 때문에 그 내용에 접근하기 위해 미리
System.IO.Compression.Filesystem
Add-Type
한다.이렇게 되면 PowerShell 자체가 다시 불러올 때마다 구성 요소를 읽는 형식으로 변하기 때문에 똑똑하지 않기 때문에 미리 읽혔는지 확인합니다.
if ("System.IO.Compression.Filesystem" -notin ([System.AppDomain]::CurrentDomain.GetAssemblies() | ForEach-Object{$_.GetName().Name})) {
Add-Type -AssemblyName System.IO.Compression.Filesystem
}
파일 열기 여부를 판단하는 방법
Docx 파일이 열려 있으면 컨텐트를 읽을 수 없으므로 먼저 컨텐트로 판정됩니다.
static [bool] IsOpened ([string]$path) {
$stream = $null
$inAccessible = $false
try {
$stream = [System.IO.FileStream]::new($path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
}
catch {
$inAccessible = $true
}
finally {
if($stream) {
$stream.Close()
}
}
return $inAccessible
}
Docx(zip) 내부에 액세스하는 방법
Docx의 확장자를 zip으로 동결해제하려면 문서의 텍스트 정보를 하위 디렉토리
word
에 포함합니다.사용
document.xml
과System.IO.StreamReader
까닭에 챙기는 것[System.IO.Compression.Zipfile]::OpenRead()
등을 잊지 않도록 주의하세요. static [string] Cat ([string]$path, [string]$relPath) {
$content = ""
$archive = [IO.Compression.Zipfile]::OpenRead($path)
$entry = $archive.GetEntry($relPath)
if ($entry) {
$stream = $entry.Open()
$reader = New-Object IO.StreamReader($stream)
$content = $reader.ReadToEnd()
$reader.Close()
$stream.Close()
}
$archive.Dispose()
return $content
}
Xml 내의 불필요한 잡음 제거 방법
워드 버전의 차이를 흡수하기 위해 Xml에
Close()
라는 요소stack overflow를 기술했다고 한다.그대로 두면 문자 메시지가 반복되기 때문에 미리 아래의 방법으로 제거한다.
또한
<mc:FallBack></mc:FallBack>
요소에 공백 문자가 있으면 <w:t></w:t>
의 내용(Xml 세척 시 공간 처리가 편리한가)을 추가합니다.앞에서 말한 바와 같이 정규 표현식에서 문자열을 추출할 때 이 부분은 매우 번거롭기 때문에 똑같이 제거한다.
static [string] RemoveNoise ([string]$s) {
return $s.Replace("<w:t xml:space=`"preserve`">", "<w:t>") -replace "<mc:FallBack>.+?</mc:FallBack>"
}
주요 방법
이전에 쓴 내용을 사용하여 Docx 파일의 Xml를 텍스트로 추출하는 것이 다음 방법입니다.주로 이 회전 형식을 사용한다.
static [PSCustomObject] GetXml ([string]$path) {
if ([Docx]::IsOpened($path)) {
return [PSCustomObject]@{
"Status" = "FILEOPENED";
"Xml" = "";
}
}
$content = [docx]::Cat($path, "word/document.xml")
return [PSCustomObject]@{
"Status" = "OK";
"Xml" = [Docx]::RemoveNoise($content);
}
}
반환값은 대상으로 파일을 열 때xml:space="preserve"
속성과 같다.Word에 대한 설명 정보는 응용 프로그램
Status
에서 확인할 수 있습니다.이것은 본래 평론이 없기 때문에
word/comments.xml
도 그 판단에 회답할 것이다. static [PSCustomObject] GetCommentsMarkup ([string]$path) {
if ([Docx]::IsOpened($path)) {
return [PSCustomObject]@{
"Status" = "FILEOPENED";
"Xml" = "";
}
}
$content = [Docx]::Cat($path, "word/comments.xml")
return [PSCustomObject]@{
"Status" = ($content)? "OK" : "NOCOMMENT";
"Xml" = $content;
}
}
Xml 내부 정보 추출 방법
Docx의 Xml 구조는 매우 복잡하여 Xml로 하는 것보다 정규적인 표현으로 매칭하는 것이 더욱 쉽다.
단락 정보 추출
Status
static [string[]] GetParagraphs ([string]$content) {
$m = [regex]::Matches($content, "<w:p [^>]+?>.+?</w:p>")
return $m.Value
}
Range<w:p></w:p>
의 추출VBA에서 자주 접촉하는 거.
Word의 버전에 따라 형식도 미묘하게 다르다.
static [string[]] GetRanges ([string]$content) {
$m = [regex]::Matches($content, "(<w:r>.+?</w:r>)|(<w:r w:[^>]+?>.+?</w:r>)")
return $m.Value
}
텍스트 정보<w:r></w:r>
의 추출상기 두 가지 방법으로 요소를 축소한 후 최종 텍스트 정보는
<w:t></w:t>
의 내용이다.이것을 뽑으면 워드 화면에서 본 내용과 같은 것을 얻을 수 있을 것이다.
static [string] GetText ([string]$nodeContent) {
$m = [regex]::Matches($nodeContent, "(?<=<w:t>).+?(?=</w:t>)")
return ($m.Value -replace "&", "&") -join ""
}
주석 정보 추출같은 생각으로 정보를 평론해도 정규 표현식으로 추출할 수 있다.
귀환 값에 상기
<w:t></w:t>
를 사용하면 주석의 텍스트 정보를 얻을 수 있습니다. static [string[]] GetComments ([string]$content) {
$m = [regex]::Matches($content, "<w:comment w:[^>]*?>.*?</w:comment>")
return $m.Value
}
원본 정보 축소
Word의 굵은 글씨체 표시와 같은 장식은 상기 Range 단위로 기록되기 때문에 직접 추출하면 깨진 정보로 변한다(Word에서 일관된 문자열이라도 여러 개의 Range가 많다).
따라서 우리도 같은 조건에 부합되는 연속 원소를 다시 배열하는 방법을 만들어 보았다.
static [string[]] FilterNode ([string[]]$nodes, [string]$pattern) {
$arr = New-Object System.Collections.ArrayList
$sb = New-Object System.Text.StringBuilder
foreach ($n in $nodes) {
if ($n -match $pattern) {
$sb.Append($n) > $null
}
else {
$s = $sb.ToString()
if ($sb.Length) {
$arr.Add($s) > $null
}
$sb.Clear()
}
}
return $arr
}
실제 사용 방법
텍스트 정보 추출
function Get-DocxTextContent {
<#
.EXAMPLE
Get-DocxTextContent .\test.docx
.EXAMPLE
ls | Get-DocxTextContent
#>
param (
[parameter(ValueFromPipeline = $true)]$inputObj
)
begin {}
process {
$fileObj = Get-Item $inputObj
if ($fileObj.Extension -ne ".docx") {
return
}
$fullPath = $fileObj.FullName
$markup = [Docx]::GetXml($fullPath)
$lines = New-Object System.Collections.ArrayList
[Docx]::GetParagraphs($markup.Xml) | ForEach-Object {
$lines.Add([Docx]::GetText($_)) > $null
}
return [PSCustomObject]@{
Name = $fileObj.Name;
Status = $markup.Status;
Lines = $lines;
}
}
end {}
}
주석 정보 추출
function Get-DocxComment {
<#
.EXAMPLE
Get-DocxComment ./hoge.docx
.EXAMPLE
ls | Get-DocxComment
#>
param (
[parameter(ValueFromPipeline = $true)]$inputObj
)
begin {}
process {
$fileObj = Get-Item $inputObj
if ($fileObj.Extension -ne ".docx") {
return
}
$markup = [Docx]::GetCommentsMarkup($fileObj.FullName)
$comments = New-Object System.Collections.ArrayList
if ($markup.Status -eq "OK") {
[Docx]::GetComments($markup.Xml) | ForEach-Object {
$comments.Add(
[PSCustomObject]@{
"Author" = [regex]::Match($_, "(?<=w:author=).+?(?= w.date)").value;
"Text" = $_ -replace "<[^>]+?>";
}
) > $null
}
}
return [PSCustomObject]@{
"Name" = $fileObj.Name;
"Status" = $markup.Status;
"Comments" = $comments;
}
}
end {}
}
굵은 정보 추출
function Get-DocxBoldString {
param (
[parameter(ValueFromPipeline = $true)]$inputObj
)
begin {}
process {
$fileObj = Get-Item $inputObj
if ($fileObj.Extension -ne ".docx") {
return
}
$fullPath = $fileObj.FullName
$markup = [Docx]::GetXml($fullPath)
$ranges = [Docx]::GetRanges($markup.Xml)
$bolds = New-Object System.Collections.ArrayList
[Docx]::FilterNode($ranges, "<w:b/>") | ForEach-Object {
$bolds.Add([Docx]::GetText($_)) > $null
}
return [PSCustomObject]@{
"Name" = $fileObj.Name;
"Status" = $markup.Status;
"Decorated" = $bolds;
}
}
end {}
}
태그 정보 추출
function Get-DocxMarkeredString {
param (
[parameter(ValueFromPipeline = $true)]$inputObj
,[string]$color = "yellow"
)
begin {}
process {
$fileObj = Get-Item $inputObj
if ($fileObj.Extension -ne ".docx") {
return
}
$fullPath = $fileObj.FullName
$markup = [Docx]::GetXml($fullPath)
$ranges = [Docx]::GetRanges($markup.Xml)
$markers = New-Object System.Collections.ArrayList
[Docx]::FilterNode($ranges, "<w:highlight w:val=`"$($color)`"/>") | ForEach-Object {
$markers.Add([Docx]::GetText($_)) > $null
}
return [PSCustomObject]@{
"Name" = $fileObj.Name;
"Status" = $markup.Status;
"Decorated" = $markers;
}
}
end {}
}
Reference
이 문제에 관하여([PowerShell] Word를 사용하지 않고 Docx 파일에서 텍스트/댓글/굵게/표기 정보를 꺼냅니다(2021년 버전)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/awtnb/articles/d0011215a39704텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)