fix dup logo
This commit is contained in:
parent
2c74317780
commit
04b2bb085d
4 changed files with 123 additions and 26 deletions
|
|
@ -142,6 +142,4 @@ Review the markdown examples to match tone, structure, and formatting convention
|
||||||
|
|
||||||
## Output
|
## Output
|
||||||
|
|
||||||
Write the generated markdown document to `~/notes/Inbox/` with the filename format `[Customer Name] POC Criteria.md`.
|
Write both the generated markdown and the rendered .odt to `~/notes/Inbox/agent/`. Use the filename format `[Customer Name] POC Criteria.md` and `[Customer Name] POC Criteria.odt`.
|
||||||
|
|
||||||
After rendering, the .odt file should be placed alongside the markdown or in the same location the user specifies.
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -27,6 +27,7 @@ import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -44,9 +45,11 @@ def find_reference_doc() -> Path:
|
||||||
# Fallback: look for the El Paso doc and warn
|
# Fallback: look for the El Paso doc and warn
|
||||||
alt = skill_dir / "examples" / "City of El Paso POC Criteria.odt"
|
alt = skill_dir / "examples" / "City of El Paso POC Criteria.odt"
|
||||||
if alt.exists():
|
if alt.exists():
|
||||||
print("Warning: using City of El Paso reference (may carry over logos). "
|
print(
|
||||||
"Run create_reference_template() to generate a clean template.",
|
"Warning: using City of El Paso reference (may carry over logos). "
|
||||||
file=sys.stderr)
|
"Run create_reference_template() to generate a clean template.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
return alt
|
return alt
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -60,13 +63,16 @@ def find_verkada_logo() -> Path:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def resize_logo_if_needed(input_path: str, max_width_inches: float = 2.75, dpi: int = 150) -> str:
|
def resize_logo_if_needed(
|
||||||
|
input_path: str, max_width_inches: float = 2.75, dpi: int = 150
|
||||||
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Resize a logo image if it exceeds the target width for print.
|
Resize a logo image if it exceeds the target width for print.
|
||||||
Returns the path to the (possibly new) resized image.
|
Returns the path to the (possibly new) resized image.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
img = Image.open(input_path)
|
img = Image.open(input_path)
|
||||||
width_px, height_px = img.size
|
width_px, height_px = img.size
|
||||||
max_width_px = int(max_width_inches * dpi)
|
max_width_px = int(max_width_inches * dpi)
|
||||||
|
|
@ -103,8 +109,8 @@ def process_markdown(
|
||||||
|
|
||||||
# Replace customer logo placeholder (match any logo comment that is NOT Verkada)
|
# Replace customer logo placeholder (match any logo comment that is NOT Verkada)
|
||||||
md_content = re.sub(
|
md_content = re.sub(
|
||||||
r'<!--\s*Image goes here:(?!.*?Verkada).*?logo.*?-->\s*\n?',
|
r"<!--\s*Image goes here:(?!.*?Verkada).*?logo.*?-->\s*\n?",
|
||||||
f'\n\n',
|
f"\n\n",
|
||||||
md_content,
|
md_content,
|
||||||
flags=re.IGNORECASE,
|
flags=re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
|
@ -112,16 +118,16 @@ def process_markdown(
|
||||||
# Replace Verkada logo placeholder
|
# Replace Verkada logo placeholder
|
||||||
if verkada_logo_path:
|
if verkada_logo_path:
|
||||||
md_content = re.sub(
|
md_content = re.sub(
|
||||||
r'<!--\s*Image goes here:.*?Verkada.*?logo.*?-->\s*\n?',
|
r"<!--\s*Image goes here:.*?Verkada.*?logo.*?-->\s*\n?",
|
||||||
f'\n',
|
f"\n",
|
||||||
md_content,
|
md_content,
|
||||||
flags=re.IGNORECASE,
|
flags=re.IGNORECASE,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Remove the Verkada logo placeholder entirely
|
# Remove the Verkada logo placeholder entirely
|
||||||
md_content = re.sub(
|
md_content = re.sub(
|
||||||
r'<!--\s*Image goes here:.*?Verkada.*?logo.*?-->\s*\n?',
|
r"<!--\s*Image goes here:.*?Verkada.*?logo.*?-->\s*\n?",
|
||||||
'',
|
"",
|
||||||
md_content,
|
md_content,
|
||||||
flags=re.IGNORECASE,
|
flags=re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
|
@ -143,10 +149,14 @@ def convert_with_pandoc(md_path: str, output_path: str, reference_doc: str) -> b
|
||||||
cmd = [
|
cmd = [
|
||||||
"pandoc",
|
"pandoc",
|
||||||
md_path,
|
md_path,
|
||||||
"-o", output_path,
|
"-o",
|
||||||
"--from", "markdown",
|
output_path,
|
||||||
"--to", "odt",
|
"--from",
|
||||||
"--reference-doc", reference_doc,
|
"markdown",
|
||||||
|
"--to",
|
||||||
|
"odt",
|
||||||
|
"--reference-doc",
|
||||||
|
reference_doc,
|
||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -156,23 +166,108 @@ def convert_with_pandoc(md_path: str, output_path: str, reference_doc: str) -> b
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print("Error: pandoc not found. Install it with your package manager.", file=sys.stderr)
|
print(
|
||||||
|
"Error: pandoc not found. Install it with your package manager.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
print("Error: pandoc timed out", file=sys.stderr)
|
print("Error: pandoc timed out", file=sys.stderr)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_elements_from_xml(
|
||||||
|
data: bytes, xpath_to_remove: list[tuple[str, dict]]
|
||||||
|
) -> bytes:
|
||||||
|
"""Remove elements matching the given xpath patterns from ODT XML data."""
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
tree = ET.fromstring(data)
|
||||||
|
changed = False
|
||||||
|
for xpath, ns in xpath_to_remove:
|
||||||
|
for el in tree.findall(xpath, ns):
|
||||||
|
for parent in tree.iter():
|
||||||
|
if el in list(parent):
|
||||||
|
parent.remove(el)
|
||||||
|
changed = True
|
||||||
|
break
|
||||||
|
if changed:
|
||||||
|
return ET.tostring(tree, encoding="unicode", xml_declaration=True).encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _odt_ns() -> dict:
|
||||||
|
return {
|
||||||
|
"text": "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
|
||||||
|
"office": "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
|
||||||
|
"style": "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
|
||||||
|
"draw": "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def remove_heading_bookmarks(odt_path: str) -> None:
|
||||||
|
"""Remove bookmark-start and bookmark-end elements from the ODT content.xml."""
|
||||||
|
ns = _odt_ns()
|
||||||
|
bookmarks_xpath = [
|
||||||
|
(".//text:bookmark", ns),
|
||||||
|
(".//text:bookmark-start", ns),
|
||||||
|
(".//text:bookmark-end", ns),
|
||||||
|
]
|
||||||
|
|
||||||
|
tmp_path = odt_path + ".tmp"
|
||||||
|
with zipfile.ZipFile(odt_path, "r") as zin:
|
||||||
|
with zipfile.ZipFile(tmp_path, "w", zipfile.ZIP_DEFLATED) as zout:
|
||||||
|
for item in zin.infolist():
|
||||||
|
data = zin.read(item.filename)
|
||||||
|
if item.filename == "content.xml":
|
||||||
|
data = _remove_elements_from_xml(data, bookmarks_xpath)
|
||||||
|
zout.writestr(item, data)
|
||||||
|
|
||||||
|
os.replace(tmp_path, odt_path)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_header_images(odt_path: str) -> None:
|
||||||
|
"""Remove draw:frame elements from the header in styles.xml to strip reference doc logos."""
|
||||||
|
ns = _odt_ns()
|
||||||
|
frames_xpath = [
|
||||||
|
(".//style:header//draw:frame", ns),
|
||||||
|
(".//style:footer//draw:frame", ns),
|
||||||
|
]
|
||||||
|
|
||||||
|
tmp_path = odt_path + ".tmp"
|
||||||
|
with zipfile.ZipFile(odt_path, "r") as zin:
|
||||||
|
with zipfile.ZipFile(tmp_path, "w", zipfile.ZIP_DEFLATED) as zout:
|
||||||
|
for item in zin.infolist():
|
||||||
|
data = zin.read(item.filename)
|
||||||
|
if item.filename == "styles.xml":
|
||||||
|
data = _remove_elements_from_xml(data, frames_xpath)
|
||||||
|
zout.writestr(item, data)
|
||||||
|
|
||||||
|
os.replace(tmp_path, odt_path)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Render a Verkada POC Criteria markdown document as a formatted .odt file."
|
description="Render a Verkada POC Criteria markdown document as a formatted .odt file."
|
||||||
)
|
)
|
||||||
parser.add_argument("--input", "-i", required=True, help="Path to the POC Criteria markdown file")
|
parser.add_argument(
|
||||||
parser.add_argument("--customer-logo", required=True, help="Path to the customer logo image")
|
"--input", "-i", required=True, help="Path to the POC Criteria markdown file"
|
||||||
parser.add_argument("--verkada-logo", help="Path to the Verkada logo image (optional)")
|
)
|
||||||
parser.add_argument("--mission", help="Customer mission statement (replaces existing)")
|
parser.add_argument(
|
||||||
|
"--customer-logo", required=True, help="Path to the customer logo image"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--verkada-logo", help="Path to the Verkada logo image (optional)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--mission", help="Customer mission statement (replaces existing)"
|
||||||
|
)
|
||||||
parser.add_argument("--reference-doc", help="Path to reference .odt for formatting")
|
parser.add_argument("--reference-doc", help="Path to reference .odt for formatting")
|
||||||
parser.add_argument("--output", "-o", help="Output .odt path (default: same as input with .odt)")
|
parser.add_argument(
|
||||||
|
"--output", "-o", help="Output .odt path (default: same as input with .odt)"
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
@ -190,9 +285,11 @@ def main():
|
||||||
# Find reference doc
|
# Find reference doc
|
||||||
reference_doc = args.reference_doc or find_reference_doc()
|
reference_doc = args.reference_doc or find_reference_doc()
|
||||||
if not reference_doc or not Path(reference_doc).exists():
|
if not reference_doc or not Path(reference_doc).exists():
|
||||||
print("Error: reference document not found. Provide --reference-doc or place the "
|
print(
|
||||||
"City of El Paso POC Criteria.odt in the skill's examples directory.",
|
"Error: reference document not found. Provide --reference-doc or place the "
|
||||||
file=sys.stderr)
|
"City of El Paso POC Criteria.odt in the skill's examples directory.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Find Verkada logo
|
# Find Verkada logo
|
||||||
|
|
@ -235,6 +332,8 @@ def main():
|
||||||
success = convert_with_pandoc(temp_md, output_path, reference_doc)
|
success = convert_with_pandoc(temp_md, output_path, reference_doc)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
|
remove_heading_bookmarks(output_path)
|
||||||
|
remove_header_images(output_path)
|
||||||
out_size = os.path.getsize(output_path)
|
out_size = os.path.getsize(output_path)
|
||||||
print(f"Done: {output_path} ({out_size:,} bytes)")
|
print(f"Done: {output_path} ({out_size:,} bytes)")
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue