python-hwpx-automation 6.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hwpx_automation/__init__.py +61 -0
- hwpx_automation/__init__.pyi +27 -0
- hwpx_automation/__main__.py +8 -0
- hwpx_automation/agent_document.py +392 -0
- hwpx_automation/api.py +136 -0
- hwpx_automation/blind_eval.py +407 -0
- hwpx_automation/capabilities.py +110 -0
- hwpx_automation/compat.py +48 -0
- hwpx_automation/configuration.py +60 -0
- hwpx_automation/core/__init__.py +2 -0
- hwpx_automation/core/content.py +762 -0
- hwpx_automation/core/context.py +111 -0
- hwpx_automation/core/diff.py +53 -0
- hwpx_automation/core/document.py +37 -0
- hwpx_automation/core/formatting.py +513 -0
- hwpx_automation/core/handles.py +24 -0
- hwpx_automation/core/locations.py +205 -0
- hwpx_automation/core/locator.py +162 -0
- hwpx_automation/core/plan.py +680 -0
- hwpx_automation/core/resources.py +42 -0
- hwpx_automation/core/search.py +296 -0
- hwpx_automation/core/transactions.py +434 -0
- hwpx_automation/core/txn.py +48 -0
- hwpx_automation/document_state.py +95 -0
- hwpx_automation/errors.py +174 -0
- hwpx_automation/execution_lock.py +15 -0
- hwpx_automation/fastmcp_adapter.py +672 -0
- hwpx_automation/form_fill.py +1177 -0
- hwpx_automation/form_output_models.py +223 -0
- hwpx_automation/handlers/__init__.py +2 -0
- hwpx_automation/handlers/_shared.py +377 -0
- hwpx_automation/handlers/agent_document.py +257 -0
- hwpx_automation/handlers/authoring.py +750 -0
- hwpx_automation/handlers/content_edit.py +1078 -0
- hwpx_automation/handlers/form_fill.py +607 -0
- hwpx_automation/handlers/layout_style.py +660 -0
- hwpx_automation/handlers/quality_render.py +566 -0
- hwpx_automation/handlers/read_export.py +1295 -0
- hwpx_automation/handlers/specialized.py +624 -0
- hwpx_automation/handlers/tracked_changes.py +589 -0
- hwpx_automation/handlers/workflow.py +105 -0
- hwpx_automation/hwp_converter.py +227 -0
- hwpx_automation/hwp_support.py +94 -0
- hwpx_automation/hwpx_ops.py +1439 -0
- hwpx_automation/identity.json +263 -0
- hwpx_automation/identity.py +18 -0
- hwpx_automation/ingest_adapters.py +85 -0
- hwpx_automation/markdown_plan.py +216 -0
- hwpx_automation/mcp_cli.py +29 -0
- hwpx_automation/metadata/tools_meta.py +40 -0
- hwpx_automation/mixed_form.py +3007 -0
- hwpx_automation/mutation_models.py +401 -0
- hwpx_automation/network_policy.py +232 -0
- hwpx_automation/office/__init__.py +14 -0
- hwpx_automation/office/agent/__init__.py +125 -0
- hwpx_automation/office/agent/_batch_verification.py +383 -0
- hwpx_automation/office/agent/blueprint/__init__.py +58 -0
- hwpx_automation/office/agent/blueprint/bundle.py +282 -0
- hwpx_automation/office/agent/blueprint/catalog.py +136 -0
- hwpx_automation/office/agent/blueprint/dump.py +520 -0
- hwpx_automation/office/agent/blueprint/mapping.py +312 -0
- hwpx_automation/office/agent/blueprint/model.py +722 -0
- hwpx_automation/office/agent/blueprint/native.py +621 -0
- hwpx_automation/office/agent/blueprint/replay.py +622 -0
- hwpx_automation/office/agent/catalog.py +252 -0
- hwpx_automation/office/agent/cli.py +647 -0
- hwpx_automation/office/agent/commands.py +1383 -0
- hwpx_automation/office/agent/document.py +801 -0
- hwpx_automation/office/agent/form_plan.py +1760 -0
- hwpx_automation/office/agent/model.py +808 -0
- hwpx_automation/office/agent/path.py +155 -0
- hwpx_automation/office/agent/query.py +230 -0
- hwpx_automation/office/agent/story.py +207 -0
- hwpx_automation/office/authoring/__init__.py +3542 -0
- hwpx_automation/office/authoring/advanced_generators.py +154 -0
- hwpx_automation/office/authoring/builder/__init__.py +52 -0
- hwpx_automation/office/authoring/builder/core.py +996 -0
- hwpx_automation/office/authoring/builder/report.py +195 -0
- hwpx_automation/office/authoring/design/__init__.py +30 -0
- hwpx_automation/office/authoring/design/_support.py +144 -0
- hwpx_automation/office/authoring/design/composer.py +282 -0
- hwpx_automation/office/authoring/design/harvest.py +305 -0
- hwpx_automation/office/authoring/design/plan.py +69 -0
- hwpx_automation/office/authoring/design/profile.py +88 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/application_form/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/profile.json +24 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/report/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/validator.py +107 -0
- hwpx_automation/office/authoring/presets/__init__.py +22 -0
- hwpx_automation/office/authoring/presets/proposal.py +538 -0
- hwpx_automation/office/authoring/report_parser.py +141 -0
- hwpx_automation/office/authoring/style_profile.py +437 -0
- hwpx_automation/office/authoring/template_analyzer.py +657 -0
- hwpx_automation/office/compliance/__init__.py +38 -0
- hwpx_automation/office/compliance/official_lint.py +478 -0
- hwpx_automation/office/compliance/pii.py +388 -0
- hwpx_automation/office/document_ops/__init__.py +13 -0
- hwpx_automation/office/document_ops/comparison.py +62 -0
- hwpx_automation/office/document_ops/mail_merge.py +73 -0
- hwpx_automation/office/document_ops/redline.py +35 -0
- hwpx_automation/office/evalplan/__init__.py +36 -0
- hwpx_automation/office/evalplan/runtime.py +2762 -0
- hwpx_automation/office/exam/__init__.py +44 -0
- hwpx_automation/office/exam/compose.py +282 -0
- hwpx_automation/office/exam/ir.py +44 -0
- hwpx_automation/office/exam/measure.py +163 -0
- hwpx_automation/office/exam/parser.py +150 -0
- hwpx_automation/office/exam/profile.py +123 -0
- hwpx_automation/office/form_fill/__init__.py +66 -0
- hwpx_automation/office/form_fill/classification.py +108 -0
- hwpx_automation/office/form_fill/fill_residue.py +242 -0
- hwpx_automation/office/form_fill/fit/__init__.py +36 -0
- hwpx_automation/office/form_fill/fit/apply.py +24 -0
- hwpx_automation/office/form_fill/fit/engine.py +24 -0
- hwpx_automation/office/form_fill/fit/measure.py +50 -0
- hwpx_automation/office/form_fill/fit/policy.py +28 -0
- hwpx_automation/office/form_fill/fit/report.py +28 -0
- hwpx_automation/office/form_fill/fit/seal.py +457 -0
- hwpx_automation/office/form_fill/fit/wordbox.py +1343 -0
- hwpx_automation/office/form_fill/guidance.py +704 -0
- hwpx_automation/office/form_fill/quality.py +961 -0
- hwpx_automation/office/form_fill/split_run.py +333 -0
- hwpx_automation/office/form_fill/template_formfit.py +656 -0
- hwpx_automation/office/house_style/__init__.py +196 -0
- hwpx_automation/office/house_style/composition.py +68 -0
- hwpx_automation/office/house_style/data/bank.json +625 -0
- hwpx_automation/office/house_style/data/genres.json +43 -0
- hwpx_automation/office/quality/__init__.py +14 -0
- hwpx_automation/office/quality/page_guard.py +277 -0
- hwpx_automation/office/rendering/__init__.py +145 -0
- hwpx_automation/office/rendering/_hancom_open_rate.ps1 +374 -0
- hwpx_automation/office/rendering/_refresh_hwpx_mac.applescript +162 -0
- hwpx_automation/office/rendering/_render_hwpx.ps1 +72 -0
- hwpx_automation/office/rendering/_render_hwpx_mac.applescript +249 -0
- hwpx_automation/office/rendering/block_splits.py +76 -0
- hwpx_automation/office/rendering/detectors.py +151 -0
- hwpx_automation/office/rendering/diff.py +153 -0
- hwpx_automation/office/rendering/fixture_corpus.py +215 -0
- hwpx_automation/office/rendering/oracle.py +909 -0
- hwpx_automation/office/rendering/page_qa.py +245 -0
- hwpx_automation/office/rendering/qa_contracts.py +293 -0
- hwpx_automation/office/rendering/qa_metrics.py +241 -0
- hwpx_automation/office/rendering/worker.py +290 -0
- hwpx_automation/office/utilities/__init__.py +12 -0
- hwpx_automation/office/utilities/table_compute.py +477 -0
- hwpx_automation/ops_services/__init__.py +1 -0
- hwpx_automation/ops_services/_border_fill.py +283 -0
- hwpx_automation/ops_services/composition.py +55 -0
- hwpx_automation/ops_services/content_layout.py +322 -0
- hwpx_automation/ops_services/context.py +213 -0
- hwpx_automation/ops_services/form_fields.py +557 -0
- hwpx_automation/ops_services/media.py +178 -0
- hwpx_automation/ops_services/memo_style.py +477 -0
- hwpx_automation/ops_services/package_validation.py +166 -0
- hwpx_automation/ops_services/planning.py +201 -0
- hwpx_automation/ops_services/preview_export.py +585 -0
- hwpx_automation/ops_services/read_query.py +601 -0
- hwpx_automation/ops_services/save_policy.py +604 -0
- hwpx_automation/ops_services/tables.py +539 -0
- hwpx_automation/ops_services/transactions.py +616 -0
- hwpx_automation/preview_output_models.py +69 -0
- hwpx_automation/public-modules.json +206 -0
- hwpx_automation/py.typed +1 -0
- hwpx_automation/quality.py +351 -0
- hwpx_automation/quality_generation.py +725 -0
- hwpx_automation/runtime.py +321 -0
- hwpx_automation/runtime_services.py +100 -0
- hwpx_automation/server.py +259 -0
- hwpx_automation/storage.py +747 -0
- hwpx_automation/tool_bindings.py +170 -0
- hwpx_automation/tool_contract.py +982 -0
- hwpx_automation/upstream.py +755 -0
- hwpx_automation/utils/__init__.py +2 -0
- hwpx_automation/utils/helpers.py +29 -0
- hwpx_automation/visual_qa.py +667 -0
- hwpx_automation/workflow/__init__.py +55 -0
- hwpx_automation/workflow/adapters.py +482 -0
- hwpx_automation/workflow/dispatcher.py +213 -0
- hwpx_automation/workflow/models.py +243 -0
- hwpx_automation/workflow/policy.py +198 -0
- hwpx_automation/workflow/render_contracts.py +173 -0
- hwpx_automation/workflow/render_metrics.py +196 -0
- hwpx_automation/workflow/render_queue.py +482 -0
- hwpx_automation/workflow/render_security.py +172 -0
- hwpx_automation/workflow/render_transport.py +369 -0
- hwpx_automation/workflow/rendering.py +206 -0
- hwpx_automation/workflow/service.py +758 -0
- hwpx_automation/workflow/state_machine.py +65 -0
- hwpx_automation/workflow/store.py +747 -0
- hwpx_automation/workspace.py +1694 -0
- python_hwpx_automation-6.0.3.dist-info/METADATA +279 -0
- python_hwpx_automation-6.0.3.dist-info/RECORD +217 -0
- python_hwpx_automation-6.0.3.dist-info/WHEEL +5 -0
- python_hwpx_automation-6.0.3.dist-info/entry_points.txt +3 -0
- python_hwpx_automation-6.0.3.dist-info/licenses/LICENSE +178 -0
- python_hwpx_automation-6.0.3.dist-info/licenses/NOTICE +14 -0
- python_hwpx_automation-6.0.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Measure the real-Hancom (한글) OPEN rate of a set of .hwpx files via COM.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
The hardened open-only oracle primitive for the M9 open-rate apparatus
|
|
7
|
+
(specs/007-open-rate). Cloned from scripts/hancom_com_open_verify.ps1 and
|
|
8
|
+
hardened per FR-002:
|
|
9
|
+
|
|
10
|
+
(a) SetMessageBoxMode(0x00020000) so corrupt-file / repair / save modals
|
|
11
|
+
do NOT block a babysat batch.
|
|
12
|
+
(b) Per-file checkpoint: each verdict record is APPENDED to --OutJsonl
|
|
13
|
+
IMMEDIATELY after Open(), so a mid-batch crash loses at most one file.
|
|
14
|
+
(c) RESUME (FR-002, this build): a re-run does NOT truncate the JSONL. On
|
|
15
|
+
startup it reads the existing checkpoint and SKIPS every file with a
|
|
16
|
+
FINAL verdict (a clean load, a clean refusal, or a completed retry). A
|
|
17
|
+
not-yet-retried COM-EXCEPTION error is NOT final and is re-attempted, so a
|
|
18
|
+
run killed mid-retry never freezes a clean file as a permanent failure.
|
|
19
|
+
After a hang/kill the owner re-runs; already-cleared clean popups are not
|
|
20
|
+
shown twice. To force a clean re-measure, delete --OutJsonl first.
|
|
21
|
+
(d) A single retry pass over files that errored on THIS run's pass 1; the
|
|
22
|
+
retried record (retried=$true) is appended and wins the basename join, so
|
|
23
|
+
the aggregator treats a retry-only open as NON-clean (never inflates the
|
|
24
|
+
opens-clean headline).
|
|
25
|
+
(e) Text-scan watchdog: the GetPageText loop (the free "parsed" tier signal)
|
|
26
|
+
is bounded by -OpenTimeoutSec so a pathological page scan cannot stall the
|
|
27
|
+
batch.
|
|
28
|
+
|
|
29
|
+
The script reuses ONE COM session for the common path (Hancom startup
|
|
30
|
+
dominates) but re-creates the session after any COM exception.
|
|
31
|
+
|
|
32
|
+
Open uses the fixed Hancom 2022 (v12) 3-arg signature Open(path,"","")
|
|
33
|
+
(auto-detect format), matching src/hwpx/visual/_render_hwpx.ps1.
|
|
34
|
+
|
|
35
|
+
*** LIMIT — a HUNG Open() ***
|
|
36
|
+
Open() is a synchronous STA COM call; it CANNOT be interrupted in-process
|
|
37
|
+
without killing the Hangul process (which would also kill an unrelated Hangul
|
|
38
|
+
the owner may have open). This script does NOT auto-kill on an Open() hang.
|
|
39
|
+
Mitigation relies on RESUME (c): if an Open() hangs, the babysitter kills the
|
|
40
|
+
run (Ctrl-C / close Hangul), re-runs, and RESUME skips every already-judged
|
|
41
|
+
file. A file that hangs Open() *persistently* is moved aside by the operator
|
|
42
|
+
(it stays unjudged → the aggregator reports it as unverified, coverage-visible).
|
|
43
|
+
An automatic per-file process-kill watchdog is deferred to the TARGET (unattended
|
|
44
|
+
CI) build and must be validated on the box before it is trusted.
|
|
45
|
+
|
|
46
|
+
*** BOX-VERIFICATION REQUIRED (FR-002, opens-clean tier) ***
|
|
47
|
+
SetMessageBoxMode(0x00020000) suppresses the modal, but the SUPPRESSED DEFAULT
|
|
48
|
+
ACTION must NOT be silent auto-repair. If Hancom's default answer to the
|
|
49
|
+
"손상된 파일을 복구하시겠습니까?" dialog is "복구"(repair), a corrupt input would be
|
|
50
|
+
auto-repaired and Open() would return $true — miscounting a broken file as
|
|
51
|
+
opens-clean. This is a Windows/Hancom-build behaviour that CANNOT be checked on
|
|
52
|
+
a Mac. The negative controls (FR-005) are the real check: the must_refuse
|
|
53
|
+
``synthetic_corrupt_section`` canary is a structurally valid package whose body
|
|
54
|
+
is garbage — if it reports opened=$true, the default action is auto-repair and
|
|
55
|
+
the harness is INVALID (corpus_open_rate.py fails closed). Open the negatives
|
|
56
|
+
FIRST (spike gate) so a leak surfaces before the full sitting.
|
|
57
|
+
|
|
58
|
+
Per-file signal: after a successful Open() the script records ``isModifiedProbe``
|
|
59
|
+
(Hwp.IsModified). This is a BOX-UNVERIFIED auto-repair hint — a clean load
|
|
60
|
+
should read $false. It is recorded for audit only; it is NOT mapped to the
|
|
61
|
+
aggregator's ``repaired`` (non-clean) field until the box run confirms it reads
|
|
62
|
+
$false for known-clean files (measure-first: an unverified property must never
|
|
63
|
+
silently collapse the headline).
|
|
64
|
+
|
|
65
|
+
Windows-only PowerShell (5.1-compatible). Syntactically validated off-box; the
|
|
66
|
+
real run happens on the .161 box.
|
|
67
|
+
|
|
68
|
+
.PARAMETER Path
|
|
69
|
+
One or more .hwpx file paths to open-check.
|
|
70
|
+
|
|
71
|
+
.PARAMETER MaxPages
|
|
72
|
+
Max pages to scan with GetPageText for the free "parsed" (textLength>0) signal.
|
|
73
|
+
|
|
74
|
+
.PARAMETER OutJsonl
|
|
75
|
+
Per-file checkpoint JSONL. One JSON object per line is APPENDED after each
|
|
76
|
+
Open(); a re-run resumes from it (skips already-judged files). Strongly
|
|
77
|
+
recommended for real (babysat) batches.
|
|
78
|
+
|
|
79
|
+
.PARAMETER OutJson
|
|
80
|
+
Optional path to write the final consolidated JSON array (prior + this run).
|
|
81
|
+
|
|
82
|
+
.PARAMETER OpenTimeoutSec
|
|
83
|
+
Bounds the per-file GetPageText scan loop (seconds). Does NOT bound a hung
|
|
84
|
+
Open() (see the LIMIT note above). Default 120.
|
|
85
|
+
|
|
86
|
+
.PARAMETER ProbeRepairMode
|
|
87
|
+
When set (and starting fresh), records the queried SetMessageBoxMode value in a
|
|
88
|
+
leading {"_meta":...} JSONL record for the receipt.
|
|
89
|
+
#>
|
|
90
|
+
param(
|
|
91
|
+
[Parameter(Mandatory = $true)]
|
|
92
|
+
[string[]] $Path,
|
|
93
|
+
|
|
94
|
+
[int] $MaxPages = 20,
|
|
95
|
+
|
|
96
|
+
[string] $OutJsonl = "",
|
|
97
|
+
|
|
98
|
+
[string] $OutJson = "",
|
|
99
|
+
|
|
100
|
+
[int] $OpenTimeoutSec = 120,
|
|
101
|
+
|
|
102
|
+
[switch] $ProbeRepairMode
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
$ErrorActionPreference = "Stop"
|
|
106
|
+
try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
|
|
107
|
+
|
|
108
|
+
# 0x00020000 = HWP_MESSAGE_BOX_MODE: auto-answer message boxes (suppress modals).
|
|
109
|
+
$MESSAGE_BOX_MODE = 0x00020000
|
|
110
|
+
|
|
111
|
+
function Resolve-InputPath {
|
|
112
|
+
param([string] $InputPath)
|
|
113
|
+
$resolved = Resolve-Path -LiteralPath $InputPath
|
|
114
|
+
return $resolved.ProviderPath
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function Copy-ToTrustedTemp {
|
|
118
|
+
param([string] $InputPath)
|
|
119
|
+
$name = [System.IO.Path]::GetFileName($InputPath)
|
|
120
|
+
$dir = Join-Path $env:TEMP ("hwpx-open-rate-" + [System.Guid]::NewGuid().ToString("N"))
|
|
121
|
+
New-Item -ItemType Directory -Force -Path $dir | Out-Null
|
|
122
|
+
$target = Join-Path $dir $name
|
|
123
|
+
Copy-Item -LiteralPath $InputPath -Destination $target -Force
|
|
124
|
+
return @{ Directory = $dir; Path = $target }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function New-HwpObject {
|
|
128
|
+
$hwp = New-Object -ComObject "HWPFrame.HwpObject"
|
|
129
|
+
try {
|
|
130
|
+
$null = $hwp.RegisterModule("FilePathCheckerModule", "FilePathCheckerModuleExample")
|
|
131
|
+
} catch {
|
|
132
|
+
Write-Warning ("RegisterModule FilePathCheckerModule failed: " + $_.Exception.Message)
|
|
133
|
+
}
|
|
134
|
+
# FR-002a: suppress modal dialogs. The negative controls (FR-005) prove the
|
|
135
|
+
# suppressed default action is NOT silent auto-repair.
|
|
136
|
+
try { $null = $hwp.SetMessageBoxMode($MESSAGE_BOX_MODE) } catch {
|
|
137
|
+
Write-Warning ("SetMessageBoxMode failed: " + $_.Exception.Message)
|
|
138
|
+
}
|
|
139
|
+
return $hwp
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function Close-HwpObject {
|
|
143
|
+
param([object] $Hwp)
|
|
144
|
+
if ($null -ne $Hwp) {
|
|
145
|
+
try { $Hwp.Quit() | Out-Null } catch {}
|
|
146
|
+
try { [System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($Hwp) | Out-Null } catch {}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function Read-HwpText {
|
|
151
|
+
param(
|
|
152
|
+
[object] $Hwp,
|
|
153
|
+
[int] $PageLimit,
|
|
154
|
+
[int] $TimeoutSec
|
|
155
|
+
)
|
|
156
|
+
# Primary: GetTextFile("TEXT","") — the canonical whole-document text extractor.
|
|
157
|
+
# Box finding 2026-07-01: GetPageText(n) returned 0 chars for ALL real produced
|
|
158
|
+
# docs on the .161 Hancom build (it depends on page-render state), which would
|
|
159
|
+
# collapse the parsed headline to 0%. GetTextFile does not depend on rendering.
|
|
160
|
+
try {
|
|
161
|
+
$whole = $Hwp.GetTextFile("TEXT", "")
|
|
162
|
+
if ($null -ne $whole -and -not [string]::IsNullOrWhiteSpace([string]$whole)) {
|
|
163
|
+
return [string]$whole
|
|
164
|
+
}
|
|
165
|
+
} catch {}
|
|
166
|
+
# Fallback: per-page scan (older builds where GetTextFile is unavailable).
|
|
167
|
+
$parts = New-Object System.Collections.Generic.List[string]
|
|
168
|
+
$sw = [System.Diagnostics.Stopwatch]::StartNew()
|
|
169
|
+
for ($page = 1; $page -le $PageLimit; $page++) {
|
|
170
|
+
# (e) text-scan watchdog: stop scanning if the page loop overruns.
|
|
171
|
+
if ($TimeoutSec -gt 0 -and $sw.Elapsed.TotalSeconds -gt $TimeoutSec) { break }
|
|
172
|
+
try {
|
|
173
|
+
$text = $Hwp.GetPageText($page)
|
|
174
|
+
} catch {
|
|
175
|
+
break
|
|
176
|
+
}
|
|
177
|
+
if ($null -eq $text -or [string]::IsNullOrWhiteSpace([string]$text)) {
|
|
178
|
+
continue
|
|
179
|
+
}
|
|
180
|
+
$parts.Add([string]$text)
|
|
181
|
+
}
|
|
182
|
+
return ($parts -join "`n")
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# Append one record as a single JSON line to the checkpoint file (FR-002b).
|
|
186
|
+
function Write-Checkpoint {
|
|
187
|
+
param(
|
|
188
|
+
[string] $JsonlPath,
|
|
189
|
+
[object] $Record
|
|
190
|
+
)
|
|
191
|
+
if (-not $JsonlPath) { return }
|
|
192
|
+
$line = ($Record | ConvertTo-Json -Depth 4 -Compress)
|
|
193
|
+
Add-Content -LiteralPath $JsonlPath -Value $line -Encoding UTF8
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# RESUME (FR-002c): basenames with a FINAL verdict in an existing checkpoint.
|
|
197
|
+
# FINAL = a clean load OR a clean refusal (error is null, opened true or false),
|
|
198
|
+
# OR a record from the single retry pass (retried=true). A COM-EXCEPTION error
|
|
199
|
+
# (error != null) with retried=false is NOT final — it is a to-be-retried state,
|
|
200
|
+
# so a resume RE-ATTEMPTS it. (Without this, a run killed mid-pass-2 would freeze a
|
|
201
|
+
# pass-1 exception as a permanent open_failure and silently DEFLATE the headline —
|
|
202
|
+
# a wrongly-low number is exactly as damaging as a wrongly-high one.) _meta probe
|
|
203
|
+
# lines have no sourcePath and are skipped.
|
|
204
|
+
function Get-JudgedBasenames {
|
|
205
|
+
param([string] $JsonlPath)
|
|
206
|
+
$set = @{}
|
|
207
|
+
if (-not $JsonlPath -or -not (Test-Path -LiteralPath $JsonlPath)) { return $set }
|
|
208
|
+
foreach ($line in (Get-Content -LiteralPath $JsonlPath -Encoding UTF8)) {
|
|
209
|
+
$line = $line.Trim()
|
|
210
|
+
if (-not $line) { continue }
|
|
211
|
+
try { $rec = $line | ConvertFrom-Json } catch { continue }
|
|
212
|
+
if ($rec.PSObject.Properties.Name -contains '_meta') { continue }
|
|
213
|
+
if (-not $rec.sourcePath) { continue }
|
|
214
|
+
$hasError = ($null -ne $rec.error) -and ([string]$rec.error -ne "")
|
|
215
|
+
$retried = [bool]$rec.retried
|
|
216
|
+
if ($hasError -and -not $retried) { continue } # unretried exception -> re-attempt on resume
|
|
217
|
+
$set[[System.IO.Path]::GetFileName([string]$rec.sourcePath)] = $true
|
|
218
|
+
}
|
|
219
|
+
return $set
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# Prior verdicts (for the consolidated OutJson array).
|
|
223
|
+
function Read-ExistingRecords {
|
|
224
|
+
param([string] $JsonlPath)
|
|
225
|
+
$list = New-Object System.Collections.Generic.List[object]
|
|
226
|
+
if (-not $JsonlPath -or -not (Test-Path -LiteralPath $JsonlPath)) { return $list }
|
|
227
|
+
foreach ($line in (Get-Content -LiteralPath $JsonlPath -Encoding UTF8)) {
|
|
228
|
+
$line = $line.Trim()
|
|
229
|
+
if (-not $line) { continue }
|
|
230
|
+
try { $rec = $line | ConvertFrom-Json } catch { continue }
|
|
231
|
+
if ($rec.PSObject.Properties.Name -contains '_meta') { continue }
|
|
232
|
+
$list.Add($rec)
|
|
233
|
+
}
|
|
234
|
+
return $list
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
# Open one file and return the verdict record. A COM exception is the watchdog
|
|
238
|
+
# trip: the caller re-creates the HwpObject when $record.error is non-null.
|
|
239
|
+
function Invoke-OpenCheck {
|
|
240
|
+
param(
|
|
241
|
+
[object] $Hwp,
|
|
242
|
+
[string] $InputPath,
|
|
243
|
+
[int] $PageLimit,
|
|
244
|
+
[int] $TimeoutSec,
|
|
245
|
+
[bool] $Retried
|
|
246
|
+
)
|
|
247
|
+
$trusted = Copy-ToTrustedTemp $InputPath
|
|
248
|
+
$opened = $false
|
|
249
|
+
$text = ""
|
|
250
|
+
$errorMessage = $null
|
|
251
|
+
$isModifiedProbe = $null
|
|
252
|
+
$pageCount = $null
|
|
253
|
+
try {
|
|
254
|
+
# Hancom 2022 (v12): Open(path, format, arg); ("","") = auto-detect.
|
|
255
|
+
# NOTE (box finding 2026-07-01): Open() returns $true even for container-
|
|
256
|
+
# garbage (not-a-zip/empty/truncated), loading a BLANK doc with textLength=0.
|
|
257
|
+
# So 'opened' alone is NOT the headline — the aggregator uses PARSED
|
|
258
|
+
# (opened AND textLength>0). pageCount is a second "content really loaded"
|
|
259
|
+
# probe captured here so the headline bar can be set from real data.
|
|
260
|
+
$opened = [bool]$Hwp.Open($trusted.Path, "", "")
|
|
261
|
+
if ($opened) {
|
|
262
|
+
# BOX-UNVERIFIED auto-repair hint (recorded for audit; not headline).
|
|
263
|
+
try { $isModifiedProbe = [bool]$Hwp.IsModified } catch { $isModifiedProbe = $null }
|
|
264
|
+
try { $pageCount = [int]$Hwp.PageCount } catch { $pageCount = $null }
|
|
265
|
+
$text = Read-HwpText -Hwp $Hwp -PageLimit $PageLimit -TimeoutSec $TimeoutSec
|
|
266
|
+
}
|
|
267
|
+
} catch {
|
|
268
|
+
$errorMessage = $_.Exception.Message
|
|
269
|
+
} finally {
|
|
270
|
+
try { $Hwp.Clear(1) | Out-Null } catch {}
|
|
271
|
+
Remove-Item -LiteralPath $trusted.Directory -Recurse -Force -ErrorAction SilentlyContinue
|
|
272
|
+
}
|
|
273
|
+
return [ordered]@{
|
|
274
|
+
sourcePath = $InputPath
|
|
275
|
+
opened = $opened
|
|
276
|
+
textLength = $text.Length
|
|
277
|
+
pageCount = $pageCount
|
|
278
|
+
textPreview = if ($text.Length -gt 500) { $text.Substring(0, 500) } else { $text }
|
|
279
|
+
error = $errorMessage
|
|
280
|
+
retried = $Retried
|
|
281
|
+
isModifiedProbe = $isModifiedProbe
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
# RESUME: load prior verdicts + the already-judged basename set. Do NOT truncate.
|
|
286
|
+
$judged = Get-JudgedBasenames $OutJsonl
|
|
287
|
+
$records = New-Object System.Collections.Generic.List[object]
|
|
288
|
+
foreach ($r in (Read-ExistingRecords $OutJsonl)) { $records.Add($r) }
|
|
289
|
+
$resuming = ($judged.Count -gt 0)
|
|
290
|
+
if ($resuming) {
|
|
291
|
+
Write-Host ("RESUME: " + $judged.Count + " file(s) already judged in " + $OutJsonl + " — skipping those.")
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if ($ProbeRepairMode -and -not $resuming) {
|
|
295
|
+
# Record the queried message-box mode for the receipt (fresh run only).
|
|
296
|
+
$probeHwp = $null
|
|
297
|
+
$modeValue = $null
|
|
298
|
+
$modeError = $null
|
|
299
|
+
try {
|
|
300
|
+
$probeHwp = New-Object -ComObject "HWPFrame.HwpObject"
|
|
301
|
+
try { $modeValue = $probeHwp.SetMessageBoxMode($MESSAGE_BOX_MODE) } catch { $modeError = $_.Exception.Message }
|
|
302
|
+
} catch {
|
|
303
|
+
$modeError = $_.Exception.Message
|
|
304
|
+
} finally {
|
|
305
|
+
Close-HwpObject $probeHwp
|
|
306
|
+
}
|
|
307
|
+
Write-Checkpoint -JsonlPath $OutJsonl -Record ([ordered]@{
|
|
308
|
+
_meta = "repair-mode-probe"
|
|
309
|
+
requestedMode = $MESSAGE_BOX_MODE
|
|
310
|
+
previousModeReturned = $modeValue
|
|
311
|
+
error = $modeError
|
|
312
|
+
note = "Box must confirm suppressed default action is NOT auto-repair; the must_refuse negative controls (FR-005) are the real check."
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
# Pass 1: open every not-yet-judged file, checkpoint immediately, re-create the
|
|
317
|
+
# session after any COM exception.
|
|
318
|
+
$thisRunErrorPaths = New-Object System.Collections.Generic.List[string]
|
|
319
|
+
$hwp = $null
|
|
320
|
+
try {
|
|
321
|
+
$hwp = New-HwpObject
|
|
322
|
+
foreach ($item in $Path) {
|
|
323
|
+
$inputPath = Resolve-InputPath $item
|
|
324
|
+
$base = [System.IO.Path]::GetFileName($inputPath)
|
|
325
|
+
if ($judged.ContainsKey($base)) {
|
|
326
|
+
Write-Host ("skip (already judged): " + $base)
|
|
327
|
+
continue
|
|
328
|
+
}
|
|
329
|
+
$record = Invoke-OpenCheck -Hwp $hwp -InputPath $inputPath -PageLimit $MaxPages -TimeoutSec $OpenTimeoutSec -Retried $false
|
|
330
|
+
Write-Checkpoint -JsonlPath $OutJsonl -Record $record
|
|
331
|
+
$records.Add($record)
|
|
332
|
+
$judged[$base] = $true
|
|
333
|
+
if ($null -ne $record.error) {
|
|
334
|
+
$thisRunErrorPaths.Add($inputPath)
|
|
335
|
+
# Watchdog: a COM error may have poisoned the session — re-create it.
|
|
336
|
+
Close-HwpObject $hwp
|
|
337
|
+
$hwp = New-HwpObject
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
} finally {
|
|
341
|
+
Close-HwpObject $hwp
|
|
342
|
+
$hwp = $null
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
# Pass 2 (FR-002d): single retry over files that errored on THIS run's pass 1. A
|
|
346
|
+
# prior-run *unretried* exception is not treated as final — RESUME re-attempts it
|
|
347
|
+
# in pass 1 above (see Get-JudgedBasenames), so it flows here again if it re-errors.
|
|
348
|
+
# The retried record is appended and wins the aggregator's last-line basename join.
|
|
349
|
+
if ($thisRunErrorPaths.Count -gt 0) {
|
|
350
|
+
$hwp = $null
|
|
351
|
+
try {
|
|
352
|
+
$hwp = New-HwpObject
|
|
353
|
+
foreach ($inputPath in $thisRunErrorPaths) {
|
|
354
|
+
$record = Invoke-OpenCheck -Hwp $hwp -InputPath $inputPath -PageLimit $MaxPages -TimeoutSec $OpenTimeoutSec -Retried $true
|
|
355
|
+
Write-Checkpoint -JsonlPath $OutJsonl -Record $record
|
|
356
|
+
$records.Add($record)
|
|
357
|
+
if ($null -ne $record.error) {
|
|
358
|
+
Close-HwpObject $hwp
|
|
359
|
+
$hwp = New-HwpObject
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
} finally {
|
|
363
|
+
Close-HwpObject $hwp
|
|
364
|
+
$hwp = $null
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
# Final consolidated array (prior + this run), mirroring the legacy shape.
|
|
369
|
+
$json = $records | ConvertTo-Json -Depth 4
|
|
370
|
+
if ($OutJson) {
|
|
371
|
+
Set-Content -LiteralPath $OutJson -Value $json -Encoding UTF8
|
|
372
|
+
} else {
|
|
373
|
+
$json
|
|
374
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
-- SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
-- Mac Hancom field-refresh backend (M7 native TOC / cross-references).
|
|
3
|
+
--
|
|
4
|
+
-- Opens an .hwpx IN PLACE, lets Hancom regenerate any dirty="1" fields
|
|
5
|
+
-- (measured semantics: a dirty TABLEOFCONTENTS is rebuilt on open — entries,
|
|
6
|
+
-- styles, and page numbers recomputed by Hancom itself; CROSSREF caches
|
|
7
|
+
-- recompute automatically), saves the document back over the same path
|
|
8
|
+
-- (파일 > 저장하기), and closes it.
|
|
9
|
+
--
|
|
10
|
+
-- Why save-then-close instead of exporting a PDF from the regenerating
|
|
11
|
+
-- session: this Hancom build crashes when PDF export runs right after an
|
|
12
|
+
-- open-time TOC regeneration (measured: deterministic %%EOF-less torsos, then
|
|
13
|
+
-- the process dies). The refreshed FILE is stable; render it in a fresh
|
|
14
|
+
-- session afterwards.
|
|
15
|
+
--
|
|
16
|
+
-- Usage: osascript _refresh_hwpx_mac.applescript <doc.hwpx> [timeoutSecs]
|
|
17
|
+
-- Output: "OK" on success; "ERR: <reason>" otherwise.
|
|
18
|
+
|
|
19
|
+
property procName : "Hancom Office HWP"
|
|
20
|
+
property appName : "Hancom Office HWP"
|
|
21
|
+
property fileMenuName : "파일"
|
|
22
|
+
property saveItemName : "저장하기"
|
|
23
|
+
property closeDocPrefix : "문서 닫기"
|
|
24
|
+
|
|
25
|
+
on run argv
|
|
26
|
+
if (count of argv) < 1 then return "ERR: usage: <doc.hwpx> [timeoutSecs]"
|
|
27
|
+
set inputPath to item 1 of argv
|
|
28
|
+
set timeoutSecs to 90
|
|
29
|
+
if (count of argv) ≥ 2 then
|
|
30
|
+
try
|
|
31
|
+
set timeoutSecs to (item 2 of argv) as integer
|
|
32
|
+
end try
|
|
33
|
+
end if
|
|
34
|
+
set inputBase to do shell script "basename " & quoted form of inputPath
|
|
35
|
+
|
|
36
|
+
try
|
|
37
|
+
do shell script "open -a " & quoted form of appName & " " & quoted form of inputPath
|
|
38
|
+
if not (waitForWindowNamed(inputBase, timeoutSecs)) then
|
|
39
|
+
return "ERR: document window did not open: " & inputBase
|
|
40
|
+
end if
|
|
41
|
+
-- Let the dirty-field regeneration settle before saving.
|
|
42
|
+
delay 3
|
|
43
|
+
|
|
44
|
+
clickFileMenuItem(saveItemName, false)
|
|
45
|
+
delay 2
|
|
46
|
+
-- A compat/format sheet may appear on save: accept the default.
|
|
47
|
+
pressReturnOnAnySheet()
|
|
48
|
+
delay 1
|
|
49
|
+
|
|
50
|
+
clickFileMenuItem(closeDocPrefix, true)
|
|
51
|
+
delay 1
|
|
52
|
+
-- If a save-changes sheet still appears, discard (we already saved).
|
|
53
|
+
dismissDontSaveSheetIfPresent()
|
|
54
|
+
return "OK"
|
|
55
|
+
on error errMsg number errNum
|
|
56
|
+
try
|
|
57
|
+
clickFileMenuItem(closeDocPrefix, true)
|
|
58
|
+
dismissDontSaveSheetIfPresent()
|
|
59
|
+
end try
|
|
60
|
+
return "ERR: " & errMsg & " (" & errNum & ")"
|
|
61
|
+
end try
|
|
62
|
+
end run
|
|
63
|
+
|
|
64
|
+
on clickFileMenuItem(itemName, prefixMatch)
|
|
65
|
+
tell application "System Events" to tell process procName
|
|
66
|
+
set frontmost to true
|
|
67
|
+
delay 0.2
|
|
68
|
+
set fileMenu to missing value
|
|
69
|
+
repeat with mbi in menu bar items of menu bar 1
|
|
70
|
+
if (name of mbi as string) is fileMenuName then
|
|
71
|
+
set fileMenu to menu 1 of mbi
|
|
72
|
+
exit repeat
|
|
73
|
+
end if
|
|
74
|
+
end repeat
|
|
75
|
+
if fileMenu is missing value then error "menu '" & fileMenuName & "' not found"
|
|
76
|
+
repeat with mi in menu items of fileMenu
|
|
77
|
+
try
|
|
78
|
+
set miName to (name of mi as string)
|
|
79
|
+
if (prefixMatch and miName starts with itemName) or (not prefixMatch and miName is itemName) then
|
|
80
|
+
click mi
|
|
81
|
+
return
|
|
82
|
+
end if
|
|
83
|
+
end try
|
|
84
|
+
end repeat
|
|
85
|
+
error "menu item not found: " & itemName
|
|
86
|
+
end tell
|
|
87
|
+
end clickFileMenuItem
|
|
88
|
+
|
|
89
|
+
on windowNames()
|
|
90
|
+
try
|
|
91
|
+
tell application "System Events" to tell process procName
|
|
92
|
+
return (name of windows) as list
|
|
93
|
+
end tell
|
|
94
|
+
on error
|
|
95
|
+
return {}
|
|
96
|
+
end try
|
|
97
|
+
end windowNames
|
|
98
|
+
|
|
99
|
+
on listContains(theList, theValue)
|
|
100
|
+
repeat with x in theList
|
|
101
|
+
if (x as string) is theValue then return true
|
|
102
|
+
end repeat
|
|
103
|
+
return false
|
|
104
|
+
end listContains
|
|
105
|
+
|
|
106
|
+
on waitForWindowNamed(winName, secs)
|
|
107
|
+
repeat (secs * 2) times
|
|
108
|
+
if listContains(windowNames(), winName) then return true
|
|
109
|
+
delay 0.5
|
|
110
|
+
end repeat
|
|
111
|
+
return false
|
|
112
|
+
end waitForWindowNamed
|
|
113
|
+
|
|
114
|
+
on pressReturnOnAnySheet()
|
|
115
|
+
repeat 4 times
|
|
116
|
+
set found to false
|
|
117
|
+
try
|
|
118
|
+
tell application "System Events" to tell process procName
|
|
119
|
+
repeat with i from 1 to (count of windows)
|
|
120
|
+
try
|
|
121
|
+
if (count of sheets of window i) > 0 then
|
|
122
|
+
set frontmost to true
|
|
123
|
+
key code 36 -- Return = default button
|
|
124
|
+
set found to true
|
|
125
|
+
end if
|
|
126
|
+
end try
|
|
127
|
+
end repeat
|
|
128
|
+
end tell
|
|
129
|
+
end try
|
|
130
|
+
if not found then return false
|
|
131
|
+
delay 0.8
|
|
132
|
+
end repeat
|
|
133
|
+
return true
|
|
134
|
+
end pressReturnOnAnySheet
|
|
135
|
+
|
|
136
|
+
on dismissDontSaveSheetIfPresent()
|
|
137
|
+
repeat 6 times
|
|
138
|
+
set handled to false
|
|
139
|
+
try
|
|
140
|
+
tell application "System Events" to tell process procName
|
|
141
|
+
repeat with i from 1 to (count of windows)
|
|
142
|
+
try
|
|
143
|
+
if (count of sheets of window i) > 0 then
|
|
144
|
+
repeat with b in buttons of (sheet 1 of window i)
|
|
145
|
+
try
|
|
146
|
+
set bn to (name of b as string)
|
|
147
|
+
if bn contains "안 함" or bn contains "안함" then
|
|
148
|
+
click b
|
|
149
|
+
set handled to true
|
|
150
|
+
end if
|
|
151
|
+
end try
|
|
152
|
+
end repeat
|
|
153
|
+
end if
|
|
154
|
+
end try
|
|
155
|
+
end repeat
|
|
156
|
+
end tell
|
|
157
|
+
end try
|
|
158
|
+
if handled then return true
|
|
159
|
+
delay 0.3
|
|
160
|
+
end repeat
|
|
161
|
+
return false
|
|
162
|
+
end dismissDontSaveSheetIfPresent
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Render .hwpx files to PDF via the Hancom (한글) COM automation object.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
The COM backend for hwpx.visual.RenderOracle. Reads a JSON job list
|
|
7
|
+
([{ "src": "...", "pdf": "..." }, ...]) so many file paths (incl. Korean
|
|
8
|
+
names) survive without argument-quoting issues, opens each through
|
|
9
|
+
HWPFrame.HwpObject, exports to PDF, and writes a JSON result array.
|
|
10
|
+
|
|
11
|
+
Hancom Office 2022 (v12) exposes Open with a fixed (filename, format, arg)
|
|
12
|
+
signature -- the 1-arg form fails to bind, so pass ("", "") for auto-detect.
|
|
13
|
+
SaveAs(path, "PDF", "") works as-is.
|
|
14
|
+
|
|
15
|
+
.PARAMETER Jobs
|
|
16
|
+
Path to the JSON job-list file (UTF-8).
|
|
17
|
+
|
|
18
|
+
.PARAMETER ResultPath
|
|
19
|
+
Optional path to write the JSON result array to; otherwise written to stdout.
|
|
20
|
+
#>
|
|
21
|
+
param(
|
|
22
|
+
[Parameter(Mandatory = $true)][string] $Jobs,
|
|
23
|
+
[string] $ResultPath = ""
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
$ErrorActionPreference = "Stop"
|
|
27
|
+
try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
|
|
28
|
+
|
|
29
|
+
$jobList = Get-Content -LiteralPath $Jobs -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
30
|
+
$results = New-Object System.Collections.Generic.List[object]
|
|
31
|
+
$hwp = $null
|
|
32
|
+
try {
|
|
33
|
+
$hwp = New-Object -ComObject "HWPFrame.HwpObject"
|
|
34
|
+
try { $null = $hwp.RegisterModule("FilePathCheckerModule", "FilePathCheckerModuleExample") } catch {}
|
|
35
|
+
# Auto-dismiss modal dialogs so automation never blocks.
|
|
36
|
+
try { $null = $hwp.SetMessageBoxMode(0x00020000) } catch {}
|
|
37
|
+
|
|
38
|
+
foreach ($job in $jobList) {
|
|
39
|
+
$src = [string]$job.src
|
|
40
|
+
$pdf = [string]$job.pdf
|
|
41
|
+
$opened = $false; $saved = $false; $err = $null
|
|
42
|
+
try {
|
|
43
|
+
$opened = [bool]$hwp.Open($src, "", "")
|
|
44
|
+
if ($opened) {
|
|
45
|
+
$saved = [bool]$hwp.SaveAs($pdf, "PDF", "")
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
$err = $_.Exception.Message
|
|
49
|
+
} finally {
|
|
50
|
+
try { $hwp.Clear(1) | Out-Null } catch {}
|
|
51
|
+
}
|
|
52
|
+
$results.Add([ordered]@{
|
|
53
|
+
src = $src
|
|
54
|
+
pdf = $pdf
|
|
55
|
+
opened = $opened
|
|
56
|
+
saved = $saved
|
|
57
|
+
error = $err
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
} finally {
|
|
61
|
+
if ($null -ne $hwp) {
|
|
62
|
+
try { $hwp.Quit() | Out-Null } catch {}
|
|
63
|
+
try { [System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($hwp) | Out-Null } catch {}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
$json = $results | ConvertTo-Json -Depth 5
|
|
68
|
+
if ($ResultPath) {
|
|
69
|
+
Set-Content -LiteralPath $ResultPath -Value $json -Encoding UTF8
|
|
70
|
+
} else {
|
|
71
|
+
$json
|
|
72
|
+
}
|