refactor: fold 'check' command into 'view <file> issues'

Text-overflow scanning now emits as DocumentIssue (Format+Warning, Id
prefix 'O') from ExcelHandler/PowerPointHandler ViewAsIssues, reusing
the existing CheckAllCellOverflow and CheckTextOverflow logic. The
standalone 'officecli check' command is removed — users migrate to
'officecli view <file> issues' (optionally '--type format').

Underlying CheckShapeTextOverflow/CheckCellOverflow/CheckAllCellOverflow
handler APIs are retained; they still back the inline overflow warning
emitted on add/set through the resident server.
This commit is contained in:
zmworm 2026-04-19 14:44:02 +08:00
parent f6e28edcd8
commit b18e52687a
5 changed files with 35 additions and 87 deletions

View file

@ -100,7 +100,7 @@ officecli view <file> <mode> # outline | stats | issues | text | annota
officecli get <file> <path> --depth N # Get a node and its children [--json]
officecli query <file> <selector> # CSS-like query
officecli validate <file> # Validate against OpenXML schema
officecli check <file> # Scan .pptx/.xlsx for text-overflow layout issues
officecli view <file> issues # Enumerate issues (text overflow, missing alt, formula errors, ...)
```
### view modes

View file

@ -54,86 +54,4 @@ static partial class CommandBuilder
return validateCommand;
}
private static Command BuildCheckCommand(Option<bool> jsonOption)
{
var checkFileArg = new Argument<FileInfo>("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;
}
}

View file

@ -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));

View file

@ -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;
}
}

View file

@ -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<Drawing.Run>().ToList();
if (runs.Count <= 1) { shapeIdx++; continue; }
if (runs.Count <= 1) continue;
var fonts = runs.Select(r =>
r.RunProperties?.GetFirstChild<Drawing.LatinFont>()?.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<Picture>())