diff --git a/SKILL.md b/SKILL.md index c86fd1c8..0dcdec30 100644 --- a/SKILL.md +++ b/SKILL.md @@ -100,7 +100,7 @@ officecli view # outline | stats | issues | text | annota officecli get --depth N # Get a node and its children [--json] officecli query # CSS-like query officecli validate # Validate against OpenXML schema -officecli check # Scan .pptx/.xlsx for text-overflow layout issues +officecli view issues # Enumerate issues (text overflow, missing alt, formula errors, ...) ``` ### view modes diff --git a/src/officecli/CommandBuilder.Check.cs b/src/officecli/CommandBuilder.Check.cs index e3e6afb8..43799b00 100644 --- a/src/officecli/CommandBuilder.Check.cs +++ b/src/officecli/CommandBuilder.Check.cs @@ -54,86 +54,4 @@ static partial class CommandBuilder return validateCommand; } - - private static Command BuildCheckCommand(Option jsonOption) - { - var checkFileArg = new Argument("file") { Description = "Office document path (.pptx, .xlsx)" }; - var checkCommand = new Command("check", "Scan document for layout issues (text overflow, etc.)"); - checkCommand.Add(checkFileArg); - checkCommand.Add(jsonOption); - checkCommand.SetAction(result => { var json = result.GetValue(jsonOption); return SafeRun(() => - { - var file = result.GetValue(checkFileArg)!; - var ext = file.Extension.ToLowerInvariant(); - if (ext != ".pptx" && ext != ".xlsx") - throw new OfficeCli.Core.CliException("The 'check' command currently supports .pptx and .xlsx files only."); - - using var handler = DocumentHandlerFactory.Open(file.FullName, editable: false); - var issues = new List<(string Path, string Message)>(); - - if (ext == ".pptx") - { - var pptHandler = handler as OfficeCli.Handlers.PowerPointHandler - ?? throw new OfficeCli.Core.CliException("Failed to open file as PowerPoint document."); - var root = pptHandler.Get("/"); - int slideCount = root?.Children?.Count ?? 0; - for (int s = 1; s <= slideCount; s++) - { - var slideNode = pptHandler.Get($"/slide[{s}]"); - int shapeCount = slideNode?.Children?.Count ?? 0; - for (int sh = 1; sh <= shapeCount; sh++) - { - var shapePath = $"/slide[{s}]/shape[{sh}]"; - var warning = pptHandler.CheckShapeTextOverflow(shapePath); - if (warning != null) - issues.Add((shapePath, warning)); - } - } - } - else // .xlsx - { - var xlHandler = handler as OfficeCli.Handlers.ExcelHandler - ?? throw new OfficeCli.Core.CliException("Failed to open file as Excel document."); - issues.AddRange(xlHandler.CheckAllCellOverflow()); - } - - if (json) - { - var arr = new System.Text.Json.Nodes.JsonArray(); - foreach (var (path, msg) in issues) - { - arr.Add((System.Text.Json.Nodes.JsonNode)new System.Text.Json.Nodes.JsonObject - { - ["path"] = path, - ["issue"] = msg - }); - } - var envelope = new System.Text.Json.Nodes.JsonObject - { - ["success"] = true, - ["file"] = file.FullName, - ["issueCount"] = issues.Count, - ["issues"] = arr - }; - Console.WriteLine(envelope.ToJsonString(OutputFormatter.PublicJsonOptions)); - } - else - { - Console.WriteLine($"Checking layout: {file.FullName}"); - if (issues.Count == 0) - { - Console.WriteLine("No layout issues found."); - } - else - { - foreach (var (path, msg) in issues) - Console.WriteLine($" {path}: {msg}"); - Console.WriteLine($"Found {issues.Count} layout issue(s)."); - } - } - return issues.Count > 0 ? 2 : 0; - }, json); }); - - return checkCommand; - } } diff --git a/src/officecli/CommandBuilder.cs b/src/officecli/CommandBuilder.cs index 199a5749..ba47f3c5 100644 --- a/src/officecli/CommandBuilder.cs +++ b/src/officecli/CommandBuilder.cs @@ -125,7 +125,6 @@ static partial class CommandBuilder rootCommand.Add(BuildRawSetCommand(jsonOption)); rootCommand.Add(BuildAddPartCommand(jsonOption)); rootCommand.Add(BuildValidateCommand(jsonOption)); - rootCommand.Add(BuildCheckCommand(jsonOption)); rootCommand.Add(BuildBatchCommand(jsonOption)); rootCommand.Add(BuildImportCommand(jsonOption)); rootCommand.Add(BuildCreateCommand(jsonOption)); diff --git a/src/officecli/Handlers/Excel/ExcelHandler.View.cs b/src/officecli/Handlers/Excel/ExcelHandler.View.cs index 50246fe1..754469d1 100644 --- a/src/officecli/Handlers/Excel/ExcelHandler.View.cs +++ b/src/officecli/Handlers/Excel/ExcelHandler.View.cs @@ -464,6 +464,21 @@ public partial class ExcelHandler } } + // CONSISTENCY(text-overflow-check): merged in from former `check` command. + // Emits wrapText-cells whose visible row-height budget can't fit the wrapped text. + foreach (var (path, msg) in CheckAllCellOverflow()) + { + if (limit.HasValue && issues.Count >= limit.Value) break; + issues.Add(new DocumentIssue + { + Id = $"O{++issueNum}", + Type = IssueType.Format, + Severity = IssueSeverity.Warning, + Path = path, + Message = msg + }); + } + return issues; } } diff --git a/src/officecli/Handlers/Pptx/PowerPointHandler.View.cs b/src/officecli/Handlers/Pptx/PowerPointHandler.View.cs index 135e2512..166f6f4e 100644 --- a/src/officecli/Handlers/Pptx/PowerPointHandler.View.cs +++ b/src/officecli/Handlers/Pptx/PowerPointHandler.View.cs @@ -457,8 +457,25 @@ public partial class PowerPointHandler int shapeIdx = 0; foreach (var shape in shapes) { + shapeIdx++; + var shapePath = $"/slide[{slideNum}]/{BuildElementPathSegment("shape", shape, shapeIdx)}"; + + // CONSISTENCY(text-overflow-check): merged in from former `check` command. + var overflow = CheckTextOverflow(shape); + if (overflow != null) + { + issues.Add(new DocumentIssue + { + Id = $"O{++issueNum}", + Type = IssueType.Format, + Severity = IssueSeverity.Warning, + Path = shapePath, + Message = overflow + }); + } + var runs = shape.Descendants().ToList(); - if (runs.Count <= 1) { shapeIdx++; continue; } + if (runs.Count <= 1) continue; var fonts = runs.Select(r => r.RunProperties?.GetFirstChild()?.Typeface @@ -472,11 +489,10 @@ public partial class PowerPointHandler Id = $"F{++issueNum}", Type = IssueType.Format, Severity = IssueSeverity.Info, - Path = $"/slide[{slideNum}]/{BuildElementPathSegment("shape", shape, shapeIdx + 1)}", + Path = shapePath, Message = $"Inconsistent fonts in text box: {string.Join(", ", fonts)}" }); } - shapeIdx++; } foreach (var pic in shapeTree.Elements())