fix: restore DOCX workbench runtime path

This commit is contained in:
codex
2026-07-30 14:00:31 +03:00
parent dab90f0780
commit 77a62d4564
13 changed files with 381 additions and 89 deletions
@@ -1,5 +1,4 @@
using System.Diagnostics;
using System.Text;
using System.Text.Json;
using DocumentAutomation.Shared;
using DocxWorkbench.Core;
@@ -10,24 +9,47 @@ namespace DocxWorkbench.App;
public sealed class MainForm : Form
{
private readonly TextBox _template = new() { Width = 520 }; private readonly TextBox _body = new() { Width = 520 }; private readonly TextBox _output = new() { Width = 520 }; private readonly TextBox _log = new() { Multiline = true, ScrollBars = ScrollBars.Vertical, Dock = DockStyle.Fill }; private readonly Label _status = new() { AutoSize = true, Text = "Готово" }; private readonly IDocumentInspector _inspector = new DocumentInspector(); private readonly IWordDocumentService _word = new WordDocumentService();
private readonly TextBox _template = new() { Width = 520 }; private readonly TextBox _body = new() { Width = 520 }; private readonly TextBox _output = new() { Width = 520 }; private readonly RichTextBox _structure = new() { Dock = DockStyle.Fill, ReadOnly = true }; private readonly RichTextBox _bodyPreview = new() { Dock = DockStyle.Fill, ReadOnly = true }; private readonly TextBox _log = new() { Multiline = true, ScrollBars = ScrollBars.Vertical, Dock = DockStyle.Fill, ReadOnly = true }; private readonly TextBox _technicalError = new() { Multiline = true, ScrollBars = ScrollBars.Vertical, Dock = DockStyle.Fill, ReadOnly = true };
private readonly Label _status = new() { AutoSize = true, Text = "Ready" }; private readonly Button _analyze; private readonly Button _build; private readonly Button _validate; private readonly Button _openDocx; private readonly Button _openPdf; private readonly WorkbenchStateController _state = new(); private readonly IDocumentInspector _docx = new OpenXmlDocumentInspector(); private readonly IDocumentInspector _txt = new TextDocumentInspector(); private readonly IWordDocumentService _word = new WordDocumentService(); private DocumentInspectionResult? _templateResult; private DocumentInspectionResult? _bodyResult; private string? _builtDocx; private string? _builtPdf;
public MainForm()
{
Text = "DOCX Workbench"; Width = 980; Height = 680; StartPosition = FormStartPosition.CenterScreen;
var grid = new TableLayoutPanel { Dock = DockStyle.Top, Height = 145, ColumnCount = 3, RowCount = 4, Padding = new Padding(8) }; grid.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 150)); grid.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); grid.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 110));
AddRow(grid, 0, "Шаблон DOCX", _template, BrowseTemplate); AddRow(grid, 1, "Тело документа", _body, BrowseBody); AddRow(grid, 2, "Выходной DOCX", _output, BrowseOutput);
var buttons = new FlowLayoutPanel { Dock = DockStyle.Fill }; buttons.Controls.Add(Button("Анализировать", async (_, _) => await InspectAsync())); buttons.Controls.Add(Button("Сформировать", async (_, _) => await BuildAsync())); buttons.Controls.Add(Button("Проверить", async (_, _) => await InspectOutputAsync())); buttons.Controls.Add(Button("Открыть DOCX", (_, _) => Open(_output.Text))); buttons.Controls.Add(Button("Открыть PDF", (_, _) => Open(Path.ChangeExtension(_output.Text, ".pdf")))); grid.Controls.Add(buttons, 1, 3); grid.SetColumnSpan(buttons, 2);
var tabs = new TabControl { Dock = DockStyle.Fill }; foreach (var title in new[] { "Предпросмотр шаблона", "Предпросмотр тела", "Структура", "Результат", "Нарушения", "Журнал" }) { var page = new TabPage(title); if (title == "Журнал") page.Controls.Add(_log); tabs.TabPages.Add(page); }
Controls.Add(tabs); Controls.Add(grid); Controls.Add(_status); _status.Dock = DockStyle.Bottom;
Text = "DOCX Workbench"; Width = 1100; Height = 720; StartPosition = FormStartPosition.CenterScreen; var grid = new TableLayoutPanel { Dock = DockStyle.Top, Height = 145, ColumnCount = 3, RowCount = 4, Padding = new Padding(8) }; grid.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 150)); grid.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); grid.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 110)); AddRow(grid, 0, "Template DOCX", _template, BrowseTemplate); AddRow(grid, 1, "Document body", _body, BrowseBody); AddRow(grid, 2, "Output DOCX", _output, BrowseOutput);
var buttons = new FlowLayoutPanel { Dock = DockStyle.Fill }; _analyze = Button("Analyze", async (_, _) => await AnalyzeAsync()); _build = Button("Build", async (_, _) => await BuildAsync()); _validate = Button("Validate", async (_, _) => await ValidateAsync()); _openDocx = Button("Open DOCX", (_, _) => Open(_builtDocx)); _openPdf = Button("Open PDF", (_, _) => Open(_builtPdf)); buttons.Controls.AddRange([_analyze, _build, _validate, _openDocx, _openPdf]); grid.Controls.Add(buttons, 1, 3); grid.SetColumnSpan(buttons, 2);
var tabs = new TabControl { Dock = DockStyle.Fill }; AddTab(tabs, "Template structure", _structure); AddTab(tabs, "Body preview", _bodyPreview); AddTab(tabs, "Result", new Label { Text = "The result becomes available after a successful build.", AutoSize = true }); AddTab(tabs, "Violations", new Label { Text = "No validation has been run.", AutoSize = true }); AddTab(tabs, "Technical error", _technicalError); AddTab(tabs, "Log", _log); Controls.Add(tabs); Controls.Add(grid); Controls.Add(_status); _status.Dock = DockStyle.Bottom;
foreach (var box in new[] { _template, _body, _output }) box.TextChanged += (_, _) => InputsChanged(); RefreshButtons();
}
private static void AddRow(TableLayoutPanel p, int row, string title, TextBox box, EventHandler browse) { p.Controls.Add(new Label { Text = title, AutoSize = true, Anchor = AnchorStyles.Left }, 0, row); p.Controls.Add(box, 1, row); var b = new Button { Text = "Обзор...", Width = 95 }; b.Click += browse; p.Controls.Add(b, 2, row); }
private static Button Button(string text, EventHandler handler) { var b = new Button { Text = text, AutoSize = true }; b.Click += handler; return b; }
private void BrowseTemplate(object? s, EventArgs e) { using var d = new OpenFileDialog { Filter = "DOCX|*.docx" }; if (d.ShowDialog() == DialogResult.OK) _template.Text = d.FileName; }
private void BrowseBody(object? s, EventArgs e) { using var d = new OpenFileDialog { Filter = "DOCX/TXT|*.docx;*.txt" }; if (d.ShowDialog() == DialogResult.OK) _body.Text = d.FileName; }
private void BrowseOutput(object? s, EventArgs e) { using var d = new SaveFileDialog { Filter = "DOCX|*.docx", DefaultExt = "docx" }; if (d.ShowDialog() == DialogResult.OK) _output.Text = d.FileName; }
private async Task InspectAsync() => await RunAsync("Анализ", () => Task.Run(() => { var a = _inspector.Inspect(_template.Text); var b = _inspector.Inspect(_body.Text); return (object)new { Template = a, Body = b }; }));
private async Task InspectOutputAsync() => await RunAsync("Проверка результата", () => Task.Run(() => (object)_inspector.Inspect(_output.Text)));
private async Task BuildAsync() => await RunAsync("Формирование", async () => { var result = await _word.BuildDocumentAsync(new WordDocumentBuildRequest { TemplatePath = _template.Text, BodyPath = _body.Text, OutputDocxPath = _output.Text }, new Progress<string>(x => _status.Text = x), CancellationToken.None); var pdf = await _word.ExportPdfAsync(result.OutputDocxPath, Path.ChangeExtension(result.OutputDocxPath, ".pdf"), CancellationToken.None); return (object)new { result, pdf }; });
private async Task RunAsync(string name, Func<Task<object>> action) { try { _status.Text = name + "..."; var value = await action(); _log.AppendText(JsonSerializer.Serialize(value, new JsonSerializerOptions { WriteIndented = true }) + Environment.NewLine); _status.Text = name + ": готово"; } catch (Exception ex) { _log.AppendText($"{name}: {ex}\r\n"); _status.Text = name + ": ошибка"; MessageBox.Show(ex.Message, name, MessageBoxButtons.OK, MessageBoxIcon.Error); } }
private static void Open(string path) { if (File.Exists(path)) Process.Start(new ProcessStartInfo(path) { UseShellExecute = true }); }
private static void AddTab(TabControl tabs, string title, Control content) { var tab = new TabPage(title); tab.Controls.Add(content); tabs.TabPages.Add(tab); }
private static void AddRow(TableLayoutPanel panel, int row, string title, TextBox box, EventHandler browse) { panel.Controls.Add(new Label { Text = title, AutoSize = true, Anchor = AnchorStyles.Left }, 0, row); panel.Controls.Add(box, 1, row); var button = new Button { Text = "Browse...", Width = 95 }; button.Click += browse; panel.Controls.Add(button, 2, row); }
private static Button Button(string text, EventHandler handler) { var button = new Button { Text = text, AutoSize = true }; button.Click += handler; return button; }
private void BrowseTemplate(object? sender, EventArgs e) { using var dialog = new OpenFileDialog { Filter = "DOCX|*.docx" }; if (dialog.ShowDialog() == DialogResult.OK) _template.Text = dialog.FileName; }
private void BrowseBody(object? sender, EventArgs e) { using var dialog = new OpenFileDialog { Filter = "DOCX or TXT|*.docx;*.txt" }; if (dialog.ShowDialog() == DialogResult.OK) _body.Text = dialog.FileName; }
private void BrowseOutput(object? sender, EventArgs e) { using var dialog = new SaveFileDialog { Filter = "DOCX|*.docx", DefaultExt = "docx" }; if (dialog.ShowDialog() == DialogResult.OK) _output.Text = dialog.FileName; }
private async Task AnalyzeAsync()
{
_templateResult = null; _bodyResult = null; _builtDocx = null; _builtPdf = null; _state.InputsSelected(File.Exists(_template.Text) && File.Exists(_body.Text)); RefreshButtons(); _status.Text = "Analyzing...";
var template = await InspectPathAsync(_template.Text); var body = await InspectPathAsync(_body.Text); if (template.Error is null) { _templateResult = template.Result; ShowStructure(template.Result); } if (body.Error is null) { _bodyResult = body.Result; ShowBody(body.Result, _body.Text); } LogInspection("Template", _template.Text, template); LogInspection("Body", _body.Text, body); var insertion = _templateResult?.InsertionPoint.Type != "None"; _state.AnalysisCompleted(_templateResult is not null, _bodyResult is not null, insertion); RefreshButtons(); _status.Text = _state.State == WorkbenchState.ReadyToBuild ? "Ready to build" : "Analysis completed with errors"; if (!insertion && _templateResult is not null) AppendLog("No DOC_BODY insertion point found. Word was not started.");
}
private Task<(DocumentInspectionResult? Result, Exception? Error, string Inspector)> InspectPathAsync(string path) => Task.Run(() => { try { if (path.EndsWith(".docx", StringComparison.OrdinalIgnoreCase)) return (_docx.Inspect(path), (Exception?)null, "OpenXmlDocumentInspector"); if (path.EndsWith(".txt", StringComparison.OrdinalIgnoreCase)) return (_txt.Inspect(path), (Exception?)null, "TextDocumentInspector"); throw new UnsupportedDocumentFormatException($"Unsupported format {Path.GetExtension(path)}. Choose DOCX or TXT."); } catch (Exception ex) { return ((DocumentInspectionResult?)null, ex, path.EndsWith(".txt", StringComparison.OrdinalIgnoreCase) ? "TextDocumentInspector" : "OpenXmlDocumentInspector"); } });
private void ShowStructure(DocumentInspectionResult? result) { if (result is null) return; _structure.Text = JsonSerializer.Serialize(new { result.FilePath, result.Sections, result.Paragraphs, result.Tables, result.Headers, result.Footers, result.Fields, result.ImageSummary, result.SectionAssignments, result.InsertionPoint, result.StyleDetails, result.Warnings }, new JsonSerializerOptions { WriteIndented = true }); }
private void ShowBody(DocumentInspectionResult? result, string path) { if (path.EndsWith(".txt", StringComparison.OrdinalIgnoreCase)) _bodyPreview.Text = File.ReadAllText(path); else if (result is not null) _bodyPreview.Text = JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }); }
private void LogInspection(string label, string path, (DocumentInspectionResult? Result, Exception? Error, string Inspector) value) { AppendLog(JsonSerializer.Serialize(new { label, path, inspector = value.Inspector, result = value.Result, error = value.Error?.Message }, new JsonSerializerOptions { WriteIndented = true })); if (value.Error is not null) _technicalError.Text = value.Error.ToString(); }
private async Task BuildAsync()
{
if (!_state.CanBuild) return; _state.BeginBuild(); RefreshButtons(); _status.Text = "Building..."; try { var result = await _word.BuildDocumentAsync(new WordDocumentBuildRequest { TemplatePath = _template.Text, BodyPath = _body.Text, OutputDocxPath = _output.Text }, new Progress<string>(x => _status.Text = x), CancellationToken.None); var pdf = await _word.ExportPdfAsync(result.OutputDocxPath, Path.ChangeExtension(result.OutputDocxPath, ".pdf"), CancellationToken.None); _builtDocx = result.OutputDocxPath; _builtPdf = pdf.PdfPath; _state.BuildSucceeded(); AppendLog(JsonSerializer.Serialize(new { result, pdf }, new JsonSerializerOptions { WriteIndented = true })); _status.Text = "Build succeeded"; } catch (Exception ex) { _state.BuildFailed(); _technicalError.Text = ex.ToString(); AppendLog("Build failed: " + ex.Message); _status.Text = "Build failed"; MessageBox.Show(ex.Message, "Build failed", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { RefreshButtons(); }
}
private async Task ValidateAsync()
{
if (!_state.CanValidate || _builtDocx is null || !File.Exists(_builtDocx)) return; _state.BeginValidation(); RefreshButtons(); _status.Text = "Validating..."; try { var result = await Task.Run(() => _docx.Inspect(_builtDocx)); _state.ValidationCompleted(true); AppendLog(JsonSerializer.Serialize(new { label = "Output", result }, new JsonSerializerOptions { WriteIndented = true })); _status.Text = "Validation succeeded"; } catch (Exception ex) { _state.ValidationCompleted(false); _technicalError.Text = ex.ToString(); AppendLog("Validation failed: " + ex.Message); _status.Text = "Validation failed"; MessageBox.Show(ex.Message, "Validation failed", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { RefreshButtons(); }
}
private void InputsChanged() { _templateResult = null; _bodyResult = null; _builtDocx = null; _builtPdf = null; _state.InputsSelected(File.Exists(_template.Text) && File.Exists(_body.Text)); RefreshButtons(); }
private void RefreshButtons() { _analyze.Enabled = File.Exists(_template.Text) && File.Exists(_body.Text); _build.Enabled = _state.CanBuild; _validate.Enabled = _state.CanValidate && _builtDocx is not null && File.Exists(_builtDocx); _openDocx.Enabled = _state.CanOpenDocx && _builtDocx is not null && File.Exists(_builtDocx); _openPdf.Enabled = _state.CanOpenPdf && _builtPdf is not null && File.Exists(_builtPdf); }
private void AppendLog(string text) => _log.AppendText(text + Environment.NewLine);
private static void Open(string? path) { if (!string.IsNullOrWhiteSpace(path) && File.Exists(path)) Process.Start(new ProcessStartInfo(path) { UseShellExecute = true }); }
}