validate.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. """
  2. Command line tool to validate Office document XML files against XSD schemas and tracked changes.
  3. Usage:
  4. python validate.py <path> [--original <original_file>] [--auto-repair] [--author NAME]
  5. The first argument can be either:
  6. - An unpacked directory containing the Office document XML files
  7. - A packed Office file (.docx/.pptx/.xlsx) which will be unpacked to a temp directory
  8. Auto-repair fixes:
  9. - paraId/durableId values that exceed OOXML limits
  10. - Missing xml:space="preserve" on w:t elements with whitespace
  11. """
  12. import argparse
  13. import sys
  14. import tempfile
  15. import zipfile
  16. from pathlib import Path
  17. from validators import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator
  18. def main():
  19. parser = argparse.ArgumentParser(description="Validate Office document XML files")
  20. parser.add_argument(
  21. "path",
  22. help="Path to unpacked directory or packed Office file (.docx/.pptx/.xlsx)",
  23. )
  24. parser.add_argument(
  25. "--original",
  26. required=False,
  27. default=None,
  28. help="Path to original file (.docx/.pptx/.xlsx). If omitted, all XSD errors are reported and redlining validation is skipped.",
  29. )
  30. parser.add_argument(
  31. "-v",
  32. "--verbose",
  33. action="store_true",
  34. help="Enable verbose output",
  35. )
  36. parser.add_argument(
  37. "--auto-repair",
  38. action="store_true",
  39. help="Automatically repair common issues (hex IDs, whitespace preservation)",
  40. )
  41. parser.add_argument(
  42. "--author",
  43. default="Claude",
  44. help="Author name for redlining validation (default: Claude)",
  45. )
  46. args = parser.parse_args()
  47. path = Path(args.path)
  48. assert path.exists(), f"Error: {path} does not exist"
  49. original_file = None
  50. if args.original:
  51. original_file = Path(args.original)
  52. assert original_file.is_file(), f"Error: {original_file} is not a file"
  53. assert original_file.suffix.lower() in [".docx", ".pptx", ".xlsx"], (
  54. f"Error: {original_file} must be a .docx, .pptx, or .xlsx file"
  55. )
  56. file_extension = (original_file or path).suffix.lower()
  57. assert file_extension in [".docx", ".pptx", ".xlsx"], (
  58. f"Error: Cannot determine file type from {path}. Use --original or provide a .docx/.pptx/.xlsx file."
  59. )
  60. if path.is_file() and path.suffix.lower() in [".docx", ".pptx", ".xlsx"]:
  61. temp_dir = tempfile.mkdtemp()
  62. with zipfile.ZipFile(path, "r") as zf:
  63. zf.extractall(temp_dir)
  64. unpacked_dir = Path(temp_dir)
  65. else:
  66. assert path.is_dir(), f"Error: {path} is not a directory or Office file"
  67. unpacked_dir = path
  68. match file_extension:
  69. case ".docx":
  70. validators = [
  71. DOCXSchemaValidator(unpacked_dir, original_file, verbose=args.verbose),
  72. ]
  73. if original_file:
  74. validators.append(
  75. RedliningValidator(unpacked_dir, original_file, verbose=args.verbose, author=args.author)
  76. )
  77. case ".pptx":
  78. validators = [
  79. PPTXSchemaValidator(unpacked_dir, original_file, verbose=args.verbose),
  80. ]
  81. case _:
  82. print(f"Error: Validation not supported for file type {file_extension}")
  83. sys.exit(1)
  84. if args.auto_repair:
  85. total_repairs = sum(v.repair() for v in validators)
  86. if total_repairs:
  87. print(f"Auto-repaired {total_repairs} issue(s)")
  88. success = all(v.validate() for v in validators)
  89. if success:
  90. print("All validations PASSED!")
  91. sys.exit(0 if success else 1)
  92. if __name__ == "__main__":
  93. main()