Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Sonnet 4.6 committed 2026-03-30 22:02:28 -04:00
1 parent 16d8426119
commit 66158e222e
398 files changed
+85259 -270637

No files matched your search

@@ -0,0 +1,30 @@
© 2025 Anthropic, PBC. All rights reserved.
LICENSE: Use of these materials (including all code, prompts, assets, files,
and other components of this Skill) is governed by your agreement with
Anthropic regarding use of Anthropic's services. If no separate agreement
exists, use is governed by Anthropic's Consumer Terms of Service or
Commercial Terms of Service, as applicable:
https://www.anthropic.com/legal/consumer-terms
https://www.anthropic.com/legal/commercial-terms
Your applicable agreement is referred to as the "Agreement." "Services" are
as defined in the Agreement.
ADDITIONAL RESTRICTIONS: Notwithstanding anything in the Agreement to the
contrary, users may not:
- Extract these materials from the Services or retain copies of these
materials outside the Services
- Reproduce or copy these materials, except for temporary copies created
automatically during authorized use of the Services
- Create derivative works based on these materials
- Distribute, sublicense, or transfer these materials to any third party
- Make, offer to sell, sell, or import any inventions embodied in these
materials
- Reverse engineer, decompile, or disassemble these materials
The receipt, viewing, or possession of these materials does not convey or
imply any license or right beyond those expressly granted above.
Anthropic retains all right, title, and interest in these materials,
including all copyrights, patents, and other intellectual property rights.
+1
View File
@@ -0,0 +1 @@
@@ -0,0 +1,135 @@
"""Accept all tracked changes in a DOCX file using LibreOffice.
Requires LibreOffice (soffice) to be installed.
"""
import argparse
import logging
import shutil
import subprocess
from pathlib import Path
from office.soffice import get_soffice_env
logger = logging.getLogger(__name__)
LIBREOFFICE_PROFILE = "/tmp/libreoffice_docx_profile"
MACRO_DIR = f"{LIBREOFFICE_PROFILE}/user/basic/Standard"
ACCEPT_CHANGES_MACRO = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="Module1" script:language="StarBasic">
Sub AcceptAllTrackedChanges()
Dim document As Object
Dim dispatcher As Object
document = ThisComponent.CurrentController.Frame
dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
dispatcher.executeDispatch(document, ".uno:AcceptAllTrackedChanges", "", 0, Array())
ThisComponent.store()
ThisComponent.close(True)
End Sub
</script:module>"""
def accept_changes(
input_file: str,
output_file: str,
) -> tuple[None, str]:
input_path = Path(input_file)
output_path = Path(output_file)
if not input_path.exists():
return None, f"Error: Input file not found: {input_file}"
if not input_path.suffix.lower() == ".docx":
return None, f"Error: Input file is not a DOCX file: {input_file}"
try:
output_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(input_path, output_path)
except Exception as e:
return None, f"Error: Failed to copy input file to output location: {e}"
if not _setup_libreoffice_macro():
return None, "Error: Failed to setup LibreOffice macro"
cmd = [
"soffice",
"--headless",
f"-env:UserInstallation=file://{LIBREOFFICE_PROFILE}",
"--norestore",
"vnd.sun.star.script:Standard.Module1.AcceptAllTrackedChanges?language=Basic&location=application",
str(output_path.absolute()),
]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=30,
check=False,
env=get_soffice_env(),
)
except subprocess.TimeoutExpired:
return (
None,
f"Successfully accepted all tracked changes: {input_file} -> {output_file}",
)
if result.returncode != 0:
return None, f"Error: LibreOffice failed: {result.stderr}"
return (
None,
f"Successfully accepted all tracked changes: {input_file} -> {output_file}",
)
def _setup_libreoffice_macro() -> bool:
macro_dir = Path(MACRO_DIR)
macro_file = macro_dir / "Module1.xba"
if macro_file.exists() and "AcceptAllTrackedChanges" in macro_file.read_text():
return True
if not macro_dir.exists():
subprocess.run(
[
"soffice",
"--headless",
f"-env:UserInstallation=file://{LIBREOFFICE_PROFILE}",
"--terminate_after_init",
],
capture_output=True,
timeout=10,
check=False,
env=get_soffice_env(),
)
macro_dir.mkdir(parents=True, exist_ok=True)
try:
macro_file.write_text(ACCEPT_CHANGES_MACRO)
return True
except Exception as e:
logger.warning(f"Failed to setup LibreOffice macro: {e}")
return False
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Accept all tracked changes in a DOCX file"
)
parser.add_argument("input_file", help="Input DOCX file with tracked changes")
parser.add_argument(
"output_file", help="Output DOCX file (clean, no tracked changes)"
)
args = parser.parse_args()
_, message = accept_changes(args.input_file, args.output_file)
print(message)
if "Error" in message:
raise SystemExit(1)
+318
View File
@@ -0,0 +1,318 @@
"""Add comments to DOCX documents.
Usage:
python comment.py unpacked/ 0 "Comment text"
python comment.py unpacked/ 1 "Reply text" --parent 0
Text should be pre-escaped XML (e.g., &amp; for &, &#x2019; for smart quotes).
After running, add markers to document.xml:
<w:commentRangeStart w:id="0"/>
... commented content ...
<w:commentRangeEnd w:id="0"/>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="0"/></w:r>
"""
import argparse
import random
import shutil
import sys
from datetime import datetime, timezone
from pathlib import Path
import defusedxml.minidom
TEMPLATE_DIR = Path(__file__).parent / "templates"
NS = {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"w14": "http://schemas.microsoft.com/office/word/2010/wordml",
"w15": "http://schemas.microsoft.com/office/word/2012/wordml",
"w16cid": "http://schemas.microsoft.com/office/word/2016/wordml/cid",
"w16cex": "http://schemas.microsoft.com/office/word/2018/wordml/cex",
}
COMMENT_XML = """\
<w:comment w:id="{id}" w:author="{author}" w:date="{date}" w:initials="{initials}">
<w:p w14:paraId="{para_id}" w14:textId="77777777">
<w:r>
<w:rPr><w:rStyle w:val="CommentReference"/></w:rPr>
<w:annotationRef/>
</w:r>
<w:r>
<w:rPr>
<w:color w:val="000000"/>
<w:sz w:val="20"/>
<w:szCs w:val="20"/>
</w:rPr>
<w:t>{text}</w:t>
</w:r>
</w:p>
</w:comment>"""
COMMENT_MARKER_TEMPLATE = """
Add to document.xml (markers must be direct children of w:p, never inside w:r):
<w:commentRangeStart w:id="{cid}"/>
<w:r>...</w:r>
<w:commentRangeEnd w:id="{cid}"/>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="{cid}"/></w:r>"""
REPLY_MARKER_TEMPLATE = """
Nest markers inside parent {pid}'s markers (markers must be direct children of w:p, never inside w:r):
<w:commentRangeStart w:id="{pid}"/><w:commentRangeStart w:id="{cid}"/>
<w:r>...</w:r>
<w:commentRangeEnd w:id="{cid}"/><w:commentRangeEnd w:id="{pid}"/>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="{pid}"/></w:r>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="{cid}"/></w:r>"""
def _generate_hex_id() -> str:
return f"{random.randint(0, 0x7FFFFFFE):08X}"
SMART_QUOTE_ENTITIES = {
"\u201c": "&#x201C;",
"\u201d": "&#x201D;",
"\u2018": "&#x2018;",
"\u2019": "&#x2019;",
}
def _encode_smart_quotes(text: str) -> str:
for char, entity in SMART_QUOTE_ENTITIES.items():
text = text.replace(char, entity)
return text
def _append_xml(xml_path: Path, root_tag: str, content: str) -> None:
dom = defusedxml.minidom.parseString(xml_path.read_text(encoding="utf-8"))
root = dom.getElementsByTagName(root_tag)[0]
ns_attrs = " ".join(f'xmlns:{k}="{v}"' for k, v in NS.items())
wrapper_dom = defusedxml.minidom.parseString(f"<root {ns_attrs}>{content}</root>")
for child in wrapper_dom.documentElement.childNodes:
if child.nodeType == child.ELEMENT_NODE:
root.appendChild(dom.importNode(child, True))
output = _encode_smart_quotes(dom.toxml(encoding="UTF-8").decode("utf-8"))
xml_path.write_text(output, encoding="utf-8")
def _find_para_id(comments_path: Path, comment_id: int) -> str | None:
dom = defusedxml.minidom.parseString(comments_path.read_text(encoding="utf-8"))
for c in dom.getElementsByTagName("w:comment"):
if c.getAttribute("w:id") == str(comment_id):
for p in c.getElementsByTagName("w:p"):
if pid := p.getAttribute("w14:paraId"):
return pid
return None
def _get_next_rid(rels_path: Path) -> int:
dom = defusedxml.minidom.parseString(rels_path.read_text(encoding="utf-8"))
max_rid = 0
for rel in dom.getElementsByTagName("Relationship"):
rid = rel.getAttribute("Id")
if rid and rid.startswith("rId"):
try:
max_rid = max(max_rid, int(rid[3:]))
except ValueError:
pass
return max_rid + 1
def _has_relationship(rels_path: Path, target: str) -> bool:
dom = defusedxml.minidom.parseString(rels_path.read_text(encoding="utf-8"))
for rel in dom.getElementsByTagName("Relationship"):
if rel.getAttribute("Target") == target:
return True
return False
def _has_content_type(ct_path: Path, part_name: str) -> bool:
dom = defusedxml.minidom.parseString(ct_path.read_text(encoding="utf-8"))
for override in dom.getElementsByTagName("Override"):
if override.getAttribute("PartName") == part_name:
return True
return False
def _ensure_comment_relationships(unpacked_dir: Path) -> None:
rels_path = unpacked_dir / "word" / "_rels" / "document.xml.rels"
if not rels_path.exists():
return
if _has_relationship(rels_path, "comments.xml"):
return
dom = defusedxml.minidom.parseString(rels_path.read_text(encoding="utf-8"))
root = dom.documentElement
next_rid = _get_next_rid(rels_path)
rels = [
(
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",
"comments.xml",
),
(
"http://schemas.microsoft.com/office/2011/relationships/commentsExtended",
"commentsExtended.xml",
),
(
"http://schemas.microsoft.com/office/2016/09/relationships/commentsIds",
"commentsIds.xml",
),
(
"http://schemas.microsoft.com/office/2018/08/relationships/commentsExtensible",
"commentsExtensible.xml",
),
]
for rel_type, target in rels:
rel = dom.createElement("Relationship")
rel.setAttribute("Id", f"rId{next_rid}")
rel.setAttribute("Type", rel_type)
rel.setAttribute("Target", target)
root.appendChild(rel)
next_rid += 1
rels_path.write_bytes(dom.toxml(encoding="UTF-8"))
def _ensure_comment_content_types(unpacked_dir: Path) -> None:
ct_path = unpacked_dir / "[Content_Types].xml"
if not ct_path.exists():
return
if _has_content_type(ct_path, "/word/comments.xml"):
return
dom = defusedxml.minidom.parseString(ct_path.read_text(encoding="utf-8"))
root = dom.documentElement
overrides = [
(
"/word/comments.xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml",
),
(
"/word/commentsExtended.xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml",
),
(
"/word/commentsIds.xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.commentsIds+xml",
),
(
"/word/commentsExtensible.xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtensible+xml",
),
]
for part_name, content_type in overrides:
override = dom.createElement("Override")
override.setAttribute("PartName", part_name)
override.setAttribute("ContentType", content_type)
root.appendChild(override)
ct_path.write_bytes(dom.toxml(encoding="UTF-8"))
def add_comment(
unpacked_dir: str,
comment_id: int,
text: str,
author: str = "Claude",
initials: str = "C",
parent_id: int | None = None,
) -> tuple[str, str]:
word = Path(unpacked_dir) / "word"
if not word.exists():
return "", f"Error: {word} not found"
para_id, durable_id = _generate_hex_id(), _generate_hex_id()
ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
comments = word / "comments.xml"
first_comment = not comments.exists()
if first_comment:
shutil.copy(TEMPLATE_DIR / "comments.xml", comments)
_ensure_comment_relationships(Path(unpacked_dir))
_ensure_comment_content_types(Path(unpacked_dir))
_append_xml(
comments,
"w:comments",
COMMENT_XML.format(
id=comment_id,
author=author,
date=ts,
initials=initials,
para_id=para_id,
text=text,
),
)
ext = word / "commentsExtended.xml"
if not ext.exists():
shutil.copy(TEMPLATE_DIR / "commentsExtended.xml", ext)
if parent_id is not None:
parent_para = _find_para_id(comments, parent_id)
if not parent_para:
return "", f"Error: Parent comment {parent_id} not found"
_append_xml(
ext,
"w15:commentsEx",
f'<w15:commentEx w15:paraId="{para_id}" w15:paraIdParent="{parent_para}" w15:done="0"/>',
)
else:
_append_xml(
ext,
"w15:commentsEx",
f'<w15:commentEx w15:paraId="{para_id}" w15:done="0"/>',
)
ids = word / "commentsIds.xml"
if not ids.exists():
shutil.copy(TEMPLATE_DIR / "commentsIds.xml", ids)
_append_xml(
ids,
"w16cid:commentsIds",
f'<w16cid:commentId w16cid:paraId="{para_id}" w16cid:durableId="{durable_id}"/>',
)
extensible = word / "commentsExtensible.xml"
if not extensible.exists():
shutil.copy(TEMPLATE_DIR / "commentsExtensible.xml", extensible)
_append_xml(
extensible,
"w16cex:commentsExtensible",
f'<w16cex:commentExtensible w16cex:durableId="{durable_id}" w16cex:dateUtc="{ts}"/>',
)
action = "reply" if parent_id is not None else "comment"
return para_id, f"Added {action} {comment_id} (para_id={para_id})"
if __name__ == "__main__":
p = argparse.ArgumentParser(description="Add comments to DOCX documents")
p.add_argument("unpacked_dir", help="Unpacked DOCX directory")
p.add_argument("comment_id", type=int, help="Comment ID (must be unique)")
p.add_argument("text", help="Comment text")
p.add_argument("--author", default="Claude", help="Author name")
p.add_argument("--initials", default="C", help="Author initials")
p.add_argument("--parent", type=int, help="Parent comment ID (for replies)")
args = p.parse_args()
para_id, msg = add_comment(
args.unpacked_dir,
args.comment_id,
args.text,
args.author,
args.initials,
args.parent,
)
print(msg)
if "Error" in msg:
sys.exit(1)
cid = args.comment_id
if args.parent is not None:
print(REPLY_MARKER_TEMPLATE.format(pid=args.parent, cid=cid))
else:
print(COMMENT_MARKER_TEMPLATE.format(cid=cid))
Whitespace-only changes.
@@ -0,0 +1,199 @@
"""Merge adjacent runs with identical formatting in DOCX.
Merges adjacent <w:r> elements that have identical <w:rPr> properties.
Works on runs in paragraphs and inside tracked changes (<w:ins>, <w:del>).
Also:
- Removes rsid attributes from runs (revision metadata that doesn't affect rendering)
- Removes proofErr elements (spell/grammar markers that block merging)
"""
from pathlib import Path
import defusedxml.minidom
def merge_runs(input_dir: str) -> tuple[int, str]:
doc_xml = Path(input_dir) / "word" / "document.xml"
if not doc_xml.exists():
return 0, f"Error: {doc_xml} not found"
try:
dom = defusedxml.minidom.parseString(doc_xml.read_text(encoding="utf-8"))
root = dom.documentElement
_remove_elements(root, "proofErr")
_strip_run_rsid_attrs(root)
containers = {run.parentNode for run in _find_elements(root, "r")}
merge_count = 0
for container in containers:
merge_count += _merge_runs_in(container)
doc_xml.write_bytes(dom.toxml(encoding="UTF-8"))
return merge_count, f"Merged {merge_count} runs"
except Exception as e:
return 0, f"Error: {e}"
def _find_elements(root, tag: str) -> list:
results = []
def traverse(node):
if node.nodeType == node.ELEMENT_NODE:
name = node.localName or node.tagName
if name == tag or name.endswith(f":{tag}"):
results.append(node)
for child in node.childNodes:
traverse(child)
traverse(root)
return results
def _get_child(parent, tag: str):
for child in parent.childNodes:
if child.nodeType == child.ELEMENT_NODE:
name = child.localName or child.tagName
if name == tag or name.endswith(f":{tag}"):
return child
return None
def _get_children(parent, tag: str) -> list:
results = []
for child in parent.childNodes:
if child.nodeType == child.ELEMENT_NODE:
name = child.localName or child.tagName
if name == tag or name.endswith(f":{tag}"):
results.append(child)
return results
def _is_adjacent(elem1, elem2) -> bool:
node = elem1.nextSibling
while node:
if node == elem2:
return True
if node.nodeType == node.ELEMENT_NODE:
return False
if node.nodeType == node.TEXT_NODE and node.data.strip():
return False
node = node.nextSibling
return False
def _remove_elements(root, tag: str):
for elem in _find_elements(root, tag):
if elem.parentNode:
elem.parentNode.removeChild(elem)
def _strip_run_rsid_attrs(root):
for run in _find_elements(root, "r"):
for attr in list(run.attributes.values()):
if "rsid" in attr.name.lower():
run.removeAttribute(attr.name)
def _merge_runs_in(container) -> int:
merge_count = 0
run = _first_child_run(container)
while run:
while True:
next_elem = _next_element_sibling(run)
if next_elem and _is_run(next_elem) and _can_merge(run, next_elem):
_merge_run_content(run, next_elem)
container.removeChild(next_elem)
merge_count += 1
else:
break
_consolidate_text(run)
run = _next_sibling_run(run)
return merge_count
def _first_child_run(container):
for child in container.childNodes:
if child.nodeType == child.ELEMENT_NODE and _is_run(child):
return child
return None
def _next_element_sibling(node):
sibling = node.nextSibling
while sibling:
if sibling.nodeType == sibling.ELEMENT_NODE:
return sibling
sibling = sibling.nextSibling
return None
def _next_sibling_run(node):
sibling = node.nextSibling
while sibling:
if sibling.nodeType == sibling.ELEMENT_NODE:
if _is_run(sibling):
return sibling
sibling = sibling.nextSibling
return None
def _is_run(node) -> bool:
name = node.localName or node.tagName
return name == "r" or name.endswith(":r")
def _can_merge(run1, run2) -> bool:
rpr1 = _get_child(run1, "rPr")
rpr2 = _get_child(run2, "rPr")
if (rpr1 is None) != (rpr2 is None):
return False
if rpr1 is None:
return True
return rpr1.toxml() == rpr2.toxml()
def _merge_run_content(target, source):
for child in list(source.childNodes):
if child.nodeType == child.ELEMENT_NODE:
name = child.localName or child.tagName
if name != "rPr" and not name.endswith(":rPr"):
target.appendChild(child)
def _consolidate_text(run):
t_elements = _get_children(run, "t")
for i in range(len(t_elements) - 1, 0, -1):
curr, prev = t_elements[i], t_elements[i - 1]
if _is_adjacent(prev, curr):
prev_text = prev.firstChild.data if prev.firstChild else ""
curr_text = curr.firstChild.data if curr.firstChild else ""
merged = prev_text + curr_text
if prev.firstChild:
prev.firstChild.data = merged
else:
prev.appendChild(run.ownerDocument.createTextNode(merged))
if merged.startswith(" ") or merged.endswith(" "):
prev.setAttribute("xml:space", "preserve")
elif prev.hasAttribute("xml:space"):
prev.removeAttribute("xml:space")
run.removeChild(curr)
@@ -0,0 +1,197 @@
"""Simplify tracked changes by merging adjacent w:ins or w:del elements.
Merges adjacent <w:ins> elements from the same author into a single element.
Same for <w:del> elements. This makes heavily-redlined documents easier to
work with by reducing the number of tracked change wrappers.
Rules:
- Only merges w:ins with w:ins, w:del with w:del (same element type)
- Only merges if same author (ignores timestamp differences)
- Only merges if truly adjacent (only whitespace between them)
"""
import xml.etree.ElementTree as ET
import zipfile
from pathlib import Path
import defusedxml.minidom
WORD_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
def simplify_redlines(input_dir: str) -> tuple[int, str]:
doc_xml = Path(input_dir) / "word" / "document.xml"
if not doc_xml.exists():
return 0, f"Error: {doc_xml} not found"
try:
dom = defusedxml.minidom.parseString(doc_xml.read_text(encoding="utf-8"))
root = dom.documentElement
merge_count = 0
containers = _find_elements(root, "p") + _find_elements(root, "tc")
for container in containers:
merge_count += _merge_tracked_changes_in(container, "ins")
merge_count += _merge_tracked_changes_in(container, "del")
doc_xml.write_bytes(dom.toxml(encoding="UTF-8"))
return merge_count, f"Simplified {merge_count} tracked changes"
except Exception as e:
return 0, f"Error: {e}"
def _merge_tracked_changes_in(container, tag: str) -> int:
merge_count = 0
tracked = [
child
for child in container.childNodes
if child.nodeType == child.ELEMENT_NODE and _is_element(child, tag)
]
if len(tracked) < 2:
return 0
i = 0
while i < len(tracked) - 1:
curr = tracked[i]
next_elem = tracked[i + 1]
if _can_merge_tracked(curr, next_elem):
_merge_tracked_content(curr, next_elem)
container.removeChild(next_elem)
tracked.pop(i + 1)
merge_count += 1
else:
i += 1
return merge_count
def _is_element(node, tag: str) -> bool:
name = node.localName or node.tagName
return name == tag or name.endswith(f":{tag}")
def _get_author(elem) -> str:
author = elem.getAttribute("w:author")
if not author:
for attr in elem.attributes.values():
if attr.localName == "author" or attr.name.endswith(":author"):
return attr.value
return author
def _can_merge_tracked(elem1, elem2) -> bool:
if _get_author(elem1) != _get_author(elem2):
return False
node = elem1.nextSibling
while node and node != elem2:
if node.nodeType == node.ELEMENT_NODE:
return False
if node.nodeType == node.TEXT_NODE and node.data.strip():
return False
node = node.nextSibling
return True
def _merge_tracked_content(target, source):
while source.firstChild:
child = source.firstChild
source.removeChild(child)
target.appendChild(child)
def _find_elements(root, tag: str) -> list:
results = []
def traverse(node):
if node.nodeType == node.ELEMENT_NODE:
name = node.localName or node.tagName
if name == tag or name.endswith(f":{tag}"):
results.append(node)
for child in node.childNodes:
traverse(child)
traverse(root)
return results
def get_tracked_change_authors(doc_xml_path: Path) -> dict[str, int]:
if not doc_xml_path.exists():
return {}
try:
tree = ET.parse(doc_xml_path)
root = tree.getroot()
except ET.ParseError:
return {}
namespaces = {"w": WORD_NS}
author_attr = f"{{{WORD_NS}}}author"
authors: dict[str, int] = {}
for tag in ["ins", "del"]:
for elem in root.findall(f".//w:{tag}", namespaces):
author = elem.get(author_attr)
if author:
authors[author] = authors.get(author, 0) + 1
return authors
def _get_authors_from_docx(docx_path: Path) -> dict[str, int]:
try:
with zipfile.ZipFile(docx_path, "r") as zf:
if "word/document.xml" not in zf.namelist():
return {}
with zf.open("word/document.xml") as f:
tree = ET.parse(f)
root = tree.getroot()
namespaces = {"w": WORD_NS}
author_attr = f"{{{WORD_NS}}}author"
authors: dict[str, int] = {}
for tag in ["ins", "del"]:
for elem in root.findall(f".//w:{tag}", namespaces):
author = elem.get(author_attr)
if author:
authors[author] = authors.get(author, 0) + 1
return authors
except (zipfile.BadZipFile, ET.ParseError):
return {}
def infer_author(modified_dir: Path, original_docx: Path, default: str = "Claude") -> str:
modified_xml = modified_dir / "word" / "document.xml"
modified_authors = get_tracked_change_authors(modified_xml)
if not modified_authors:
return default
original_authors = _get_authors_from_docx(original_docx)
new_changes: dict[str, int] = {}
for author, count in modified_authors.items():
original_count = original_authors.get(author, 0)
diff = count - original_count
if diff > 0:
new_changes[author] = diff
if not new_changes:
return default
if len(new_changes) == 1:
return next(iter(new_changes))
raise ValueError(
f"Multiple authors added new changes: {new_changes}. "
"Cannot infer which author to validate."
)
+159
View File
@@ -0,0 +1,159 @@
"""Pack a directory into a DOCX, PPTX, or XLSX file.
Validates with auto-repair, condenses XML formatting, and creates the Office file.
Usage:
python pack.py <input_directory> <output_file> [--original <file>] [--validate true|false]
Examples:
python pack.py unpacked/ output.docx --original input.docx
python pack.py unpacked/ output.pptx --validate false
"""
import argparse
import sys
import shutil
import tempfile
import zipfile
from pathlib import Path
import defusedxml.minidom
from validators import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator
def pack(
input_directory: str,
output_file: str,
original_file: str | None = None,
validate: bool = True,
infer_author_func=None,
) -> tuple[None, str]:
input_dir = Path(input_directory)
output_path = Path(output_file)
suffix = output_path.suffix.lower()
if not input_dir.is_dir():
return None, f"Error: {input_dir} is not a directory"
if suffix not in {".docx", ".pptx", ".xlsx"}:
return None, f"Error: {output_file} must be a .docx, .pptx, or .xlsx file"
if validate and original_file:
original_path = Path(original_file)
if original_path.exists():
success, output = _run_validation(
input_dir, original_path, suffix, infer_author_func
)
if output:
print(output)
if not success:
return None, f"Error: Validation failed for {input_dir}"
with tempfile.TemporaryDirectory() as temp_dir:
temp_content_dir = Path(temp_dir) / "content"
shutil.copytree(input_dir, temp_content_dir)
for pattern in ["*.xml", "*.rels"]:
for xml_file in temp_content_dir.rglob(pattern):
_condense_xml(xml_file)
output_path.parent.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
for f in temp_content_dir.rglob("*"):
if f.is_file():
zf.write(f, f.relative_to(temp_content_dir))
return None, f"Successfully packed {input_dir} to {output_file}"
def _run_validation(
unpacked_dir: Path,
original_file: Path,
suffix: str,
infer_author_func=None,
) -> tuple[bool, str | None]:
output_lines = []
validators = []
if suffix == ".docx":
author = "Claude"
if infer_author_func:
try:
author = infer_author_func(unpacked_dir, original_file)
except ValueError as e:
print(f"Warning: {e} Using default author 'Claude'.", file=sys.stderr)
validators = [
DOCXSchemaValidator(unpacked_dir, original_file),
RedliningValidator(unpacked_dir, original_file, author=author),
]
elif suffix == ".pptx":
validators = [PPTXSchemaValidator(unpacked_dir, original_file)]
if not validators:
return True, None
total_repairs = sum(v.repair() for v in validators)
if total_repairs:
output_lines.append(f"Auto-repaired {total_repairs} issue(s)")
success = all(v.validate() for v in validators)
if success:
output_lines.append("All validations PASSED!")
return success, "\n".join(output_lines) if output_lines else None
def _condense_xml(xml_file: Path) -> None:
try:
with open(xml_file, encoding="utf-8") as f:
dom = defusedxml.minidom.parse(f)
for element in dom.getElementsByTagName("*"):
if element.tagName.endswith(":t"):
continue
for child in list(element.childNodes):
if (
child.nodeType == child.TEXT_NODE
and child.nodeValue
and child.nodeValue.strip() == ""
) or child.nodeType == child.COMMENT_NODE:
element.removeChild(child)
xml_file.write_bytes(dom.toxml(encoding="UTF-8"))
except Exception as e:
print(f"ERROR: Failed to parse {xml_file.name}: {e}", file=sys.stderr)
raise
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Pack a directory into a DOCX, PPTX, or XLSX file"
)
parser.add_argument("input_directory", help="Unpacked Office document directory")
parser.add_argument("output_file", help="Output Office file (.docx/.pptx/.xlsx)")
parser.add_argument(
"--original",
help="Original file for validation comparison",
)
parser.add_argument(
"--validate",
type=lambda x: x.lower() == "true",
default=True,
metavar="true|false",
help="Run validation with auto-repair (default: true)",
)
args = parser.parse_args()
_, message = pack(
args.input_directory,
args.output_file,
original_file=args.original,
validate=args.validate,
)
print(message)
if "Error" in message:
sys.exit(1)
@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
xmlns="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing"
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing"
elementFormDefault="qualified">
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
schemaLocation="dml-main.xsd"/>
<xsd:complexType name="CT_ShapeNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1" maxOccurs="1"
/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Shape">
<xsd:sequence>
<xsd:element name="nvSpPr" type="CT_ShapeNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
<xsd:element name="txBody" type="a:CT_TextBody" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
<xsd:attribute name="textlink" type="xsd:string" use="optional"/>
<xsd:attribute name="fLocksText" type="xsd:boolean" use="optional" default="true"/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_ConnectorNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvCxnSpPr" type="a:CT_NonVisualConnectorProperties" minOccurs="1"
maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Connector">
<xsd:sequence>
<xsd:element name="nvCxnSpPr" type="CT_ConnectorNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_PictureNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvPicPr" type="a:CT_NonVisualPictureProperties" minOccurs="1"
maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Picture">
<xsd:sequence>
<xsd:element name="nvPicPr" type="CT_PictureNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="blipFill" type="a:CT_BlipFillProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional" default=""/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_GraphicFrameNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GraphicFrame">
<xsd:sequence>
<xsd:element name="nvGraphicFramePr" type="CT_GraphicFrameNonVisual" minOccurs="1"
maxOccurs="1"/>
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="1" maxOccurs="1"/>
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_GroupShapeNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvGrpSpPr" type="a:CT_NonVisualGroupDrawingShapeProps" minOccurs="1"
maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GroupShape">
<xsd:sequence>
<xsd:element name="nvGrpSpPr" type="CT_GroupShapeNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="grpSpPr" type="a:CT_GroupShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="sp" type="CT_Shape"/>
<xsd:element name="grpSp" type="CT_GroupShape"/>
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
<xsd:element name="cxnSp" type="CT_Connector"/>
<xsd:element name="pic" type="CT_Picture"/>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
<xsd:group name="EG_ObjectChoices">
<xsd:sequence>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="sp" type="CT_Shape"/>
<xsd:element name="grpSp" type="CT_GroupShape"/>
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
<xsd:element name="cxnSp" type="CT_Connector"/>
<xsd:element name="pic" type="CT_Picture"/>
</xsd:choice>
</xsd:sequence>
</xsd:group>
<xsd:simpleType name="ST_MarkerCoordinate">
<xsd:restriction base="xsd:double">
<xsd:minInclusive value="0.0"/>
<xsd:maxInclusive value="1.0"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Marker">
<xsd:sequence>
<xsd:element name="x" type="ST_MarkerCoordinate" minOccurs="1" maxOccurs="1"/>
<xsd:element name="y" type="ST_MarkerCoordinate" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_RelSizeAnchor">
<xsd:sequence>
<xsd:element name="from" type="CT_Marker"/>
<xsd:element name="to" type="CT_Marker"/>
<xsd:group ref="EG_ObjectChoices"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_AbsSizeAnchor">
<xsd:sequence>
<xsd:element name="from" type="CT_Marker"/>
<xsd:element name="ext" type="a:CT_PositiveSize2D"/>
<xsd:group ref="EG_ObjectChoices"/>
</xsd:sequence>
</xsd:complexType>
<xsd:group name="EG_Anchor">
<xsd:choice>
<xsd:element name="relSizeAnchor" type="CT_RelSizeAnchor"/>
<xsd:element name="absSizeAnchor" type="CT_AbsSizeAnchor"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_Drawing">
<xsd:sequence>
<xsd:group ref="EG_Anchor" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas"
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
elementFormDefault="qualified"
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas">
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
schemaLocation="dml-main.xsd"/>
<xsd:element name="lockedCanvas" type="a:CT_GvmlGroupShape"/>
</xsd:schema>
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/drawingml/2006/picture"
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" elementFormDefault="qualified"
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/picture">
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
schemaLocation="dml-main.xsd"/>
<xsd:complexType name="CT_PictureNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvPicPr" type="a:CT_NonVisualPictureProperties" minOccurs="1"
maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Picture">
<xsd:sequence minOccurs="1" maxOccurs="1">
<xsd:element name="nvPicPr" type="CT_PictureNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="blipFill" type="a:CT_BlipFillProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="pic" type="CT_Picture"/>
</xsd:schema>
@@ -0,0 +1,185 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
xmlns="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
elementFormDefault="qualified">
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
schemaLocation="dml-main.xsd"/>
<xsd:import schemaLocation="shared-relationshipReference.xsd"
namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"/>
<xsd:element name="from" type="CT_Marker"/>
<xsd:element name="to" type="CT_Marker"/>
<xsd:complexType name="CT_AnchorClientData">
<xsd:attribute name="fLocksWithSheet" type="xsd:boolean" use="optional" default="true"/>
<xsd:attribute name="fPrintsWithSheet" type="xsd:boolean" use="optional" default="true"/>
</xsd:complexType>
<xsd:complexType name="CT_ShapeNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1" maxOccurs="1"
/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Shape">
<xsd:sequence>
<xsd:element name="nvSpPr" type="CT_ShapeNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
<xsd:element name="txBody" type="a:CT_TextBody" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
<xsd:attribute name="textlink" type="xsd:string" use="optional"/>
<xsd:attribute name="fLocksText" type="xsd:boolean" use="optional" default="true"/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_ConnectorNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvCxnSpPr" type="a:CT_NonVisualConnectorProperties" minOccurs="1"
maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Connector">
<xsd:sequence>
<xsd:element name="nvCxnSpPr" type="CT_ConnectorNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_PictureNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvPicPr" type="a:CT_NonVisualPictureProperties" minOccurs="1"
maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Picture">
<xsd:sequence>
<xsd:element name="nvPicPr" type="CT_PictureNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="blipFill" type="a:CT_BlipFillProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional" default=""/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_GraphicalObjectFrameNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GraphicalObjectFrame">
<xsd:sequence>
<xsd:element name="nvGraphicFramePr" type="CT_GraphicalObjectFrameNonVisual" minOccurs="1"
maxOccurs="1"/>
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="1" maxOccurs="1"/>
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_GroupShapeNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvGrpSpPr" type="a:CT_NonVisualGroupDrawingShapeProps" minOccurs="1"
maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GroupShape">
<xsd:sequence>
<xsd:element name="nvGrpSpPr" type="CT_GroupShapeNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="grpSpPr" type="a:CT_GroupShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="sp" type="CT_Shape"/>
<xsd:element name="grpSp" type="CT_GroupShape"/>
<xsd:element name="graphicFrame" type="CT_GraphicalObjectFrame"/>
<xsd:element name="cxnSp" type="CT_Connector"/>
<xsd:element name="pic" type="CT_Picture"/>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
<xsd:group name="EG_ObjectChoices">
<xsd:sequence>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="sp" type="CT_Shape"/>
<xsd:element name="grpSp" type="CT_GroupShape"/>
<xsd:element name="graphicFrame" type="CT_GraphicalObjectFrame"/>
<xsd:element name="cxnSp" type="CT_Connector"/>
<xsd:element name="pic" type="CT_Picture"/>
<xsd:element name="contentPart" type="CT_Rel"/>
</xsd:choice>
</xsd:sequence>
</xsd:group>
<xsd:complexType name="CT_Rel">
<xsd:attribute ref="r:id" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_ColID">
<xsd:restriction base="xsd:int">
<xsd:minInclusive value="0"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_RowID">
<xsd:restriction base="xsd:int">
<xsd:minInclusive value="0"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Marker">
<xsd:sequence>
<xsd:element name="col" type="ST_ColID"/>
<xsd:element name="colOff" type="a:ST_Coordinate"/>
<xsd:element name="row" type="ST_RowID"/>
<xsd:element name="rowOff" type="a:ST_Coordinate"/>
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="ST_EditAs">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="twoCell"/>
<xsd:enumeration value="oneCell"/>
<xsd:enumeration value="absolute"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_TwoCellAnchor">
<xsd:sequence>
<xsd:element name="from" type="CT_Marker"/>
<xsd:element name="to" type="CT_Marker"/>
<xsd:group ref="EG_ObjectChoices"/>
<xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="editAs" type="ST_EditAs" use="optional" default="twoCell"/>
</xsd:complexType>
<xsd:complexType name="CT_OneCellAnchor">
<xsd:sequence>
<xsd:element name="from" type="CT_Marker"/>
<xsd:element name="ext" type="a:CT_PositiveSize2D"/>
<xsd:group ref="EG_ObjectChoices"/>
<xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_AbsoluteAnchor">
<xsd:sequence>
<xsd:element name="pos" type="a:CT_Point2D"/>
<xsd:element name="ext" type="a:CT_PositiveSize2D"/>
<xsd:group ref="EG_ObjectChoices"/>
<xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:group name="EG_Anchor">
<xsd:choice>
<xsd:element name="twoCellAnchor" type="CT_TwoCellAnchor"/>
<xsd:element name="oneCellAnchor" type="CT_OneCellAnchor"/>
<xsd:element name="absoluteAnchor" type="CT_AbsoluteAnchor"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_Drawing">
<xsd:sequence>
<xsd:group ref="EG_Anchor" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="wsDr" type="CT_Drawing"/>
</xsd:schema>
@@ -0,0 +1,287 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:dpct="http://schemas.openxmlformats.org/drawingml/2006/picture"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
elementFormDefault="qualified">
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
schemaLocation="dml-main.xsd"/>
<xsd:import schemaLocation="wml.xsd"
namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/picture"
schemaLocation="dml-picture.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
schemaLocation="shared-relationshipReference.xsd"/>
<xsd:complexType name="CT_EffectExtent">
<xsd:attribute name="l" type="a:ST_Coordinate" use="required"/>
<xsd:attribute name="t" type="a:ST_Coordinate" use="required"/>
<xsd:attribute name="r" type="a:ST_Coordinate" use="required"/>
<xsd:attribute name="b" type="a:ST_Coordinate" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_WrapDistance">
<xsd:restriction base="xsd:unsignedInt"/>
</xsd:simpleType>
<xsd:complexType name="CT_Inline">
<xsd:sequence>
<xsd:element name="extent" type="a:CT_PositiveSize2D"/>
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
<xsd:element name="docPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
minOccurs="0" maxOccurs="1"/>
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="ST_WrapText">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="bothSides"/>
<xsd:enumeration value="left"/>
<xsd:enumeration value="right"/>
<xsd:enumeration value="largest"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_WrapPath">
<xsd:sequence>
<xsd:element name="start" type="a:CT_Point2D" minOccurs="1" maxOccurs="1"/>
<xsd:element name="lineTo" type="a:CT_Point2D" minOccurs="2" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="edited" type="xsd:boolean" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_WrapNone"/>
<xsd:complexType name="CT_WrapSquare">
<xsd:sequence>
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="wrapText" type="ST_WrapText" use="required"/>
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_WrapTight">
<xsd:sequence>
<xsd:element name="wrapPolygon" type="CT_WrapPath" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="wrapText" type="ST_WrapText" use="required"/>
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_WrapThrough">
<xsd:sequence>
<xsd:element name="wrapPolygon" type="CT_WrapPath" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="wrapText" type="ST_WrapText" use="required"/>
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_WrapTopBottom">
<xsd:sequence>
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
</xsd:complexType>
<xsd:group name="EG_WrapType">
<xsd:sequence>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="wrapNone" type="CT_WrapNone" minOccurs="1" maxOccurs="1"/>
<xsd:element name="wrapSquare" type="CT_WrapSquare" minOccurs="1" maxOccurs="1"/>
<xsd:element name="wrapTight" type="CT_WrapTight" minOccurs="1" maxOccurs="1"/>
<xsd:element name="wrapThrough" type="CT_WrapThrough" minOccurs="1" maxOccurs="1"/>
<xsd:element name="wrapTopAndBottom" type="CT_WrapTopBottom" minOccurs="1" maxOccurs="1"/>
</xsd:choice>
</xsd:sequence>
</xsd:group>
<xsd:simpleType name="ST_PositionOffset">
<xsd:restriction base="xsd:int"/>
</xsd:simpleType>
<xsd:simpleType name="ST_AlignH">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="left"/>
<xsd:enumeration value="right"/>
<xsd:enumeration value="center"/>
<xsd:enumeration value="inside"/>
<xsd:enumeration value="outside"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_RelFromH">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="margin"/>
<xsd:enumeration value="page"/>
<xsd:enumeration value="column"/>
<xsd:enumeration value="character"/>
<xsd:enumeration value="leftMargin"/>
<xsd:enumeration value="rightMargin"/>
<xsd:enumeration value="insideMargin"/>
<xsd:enumeration value="outsideMargin"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_PosH">
<xsd:sequence>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="align" type="ST_AlignH" minOccurs="1" maxOccurs="1"/>
<xsd:element name="posOffset" type="ST_PositionOffset" minOccurs="1" maxOccurs="1"/>
</xsd:choice>
</xsd:sequence>
<xsd:attribute name="relativeFrom" type="ST_RelFromH" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_AlignV">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="top"/>
<xsd:enumeration value="bottom"/>
<xsd:enumeration value="center"/>
<xsd:enumeration value="inside"/>
<xsd:enumeration value="outside"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_RelFromV">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="margin"/>
<xsd:enumeration value="page"/>
<xsd:enumeration value="paragraph"/>
<xsd:enumeration value="line"/>
<xsd:enumeration value="topMargin"/>
<xsd:enumeration value="bottomMargin"/>
<xsd:enumeration value="insideMargin"/>
<xsd:enumeration value="outsideMargin"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_PosV">
<xsd:sequence>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="align" type="ST_AlignV" minOccurs="1" maxOccurs="1"/>
<xsd:element name="posOffset" type="ST_PositionOffset" minOccurs="1" maxOccurs="1"/>
</xsd:choice>
</xsd:sequence>
<xsd:attribute name="relativeFrom" type="ST_RelFromV" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_Anchor">
<xsd:sequence>
<xsd:element name="simplePos" type="a:CT_Point2D"/>
<xsd:element name="positionH" type="CT_PosH"/>
<xsd:element name="positionV" type="CT_PosV"/>
<xsd:element name="extent" type="a:CT_PositiveSize2D"/>
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
<xsd:group ref="EG_WrapType"/>
<xsd:element name="docPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
minOccurs="0" maxOccurs="1"/>
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="simplePos" type="xsd:boolean"/>
<xsd:attribute name="relativeHeight" type="xsd:unsignedInt" use="required"/>
<xsd:attribute name="behindDoc" type="xsd:boolean" use="required"/>
<xsd:attribute name="locked" type="xsd:boolean" use="required"/>
<xsd:attribute name="layoutInCell" type="xsd:boolean" use="required"/>
<xsd:attribute name="hidden" type="xsd:boolean" use="optional"/>
<xsd:attribute name="allowOverlap" type="xsd:boolean" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_TxbxContent">
<xsd:group ref="w:EG_BlockLevelElts" minOccurs="1" maxOccurs="unbounded"/>
</xsd:complexType>
<xsd:complexType name="CT_TextboxInfo">
<xsd:sequence>
<xsd:element name="txbxContent" type="CT_TxbxContent" minOccurs="1" maxOccurs="1"/>
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:unsignedShort" use="optional" default="0"/>
</xsd:complexType>
<xsd:complexType name="CT_LinkedTextboxInformation">
<xsd:sequence>
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:unsignedShort" use="required"/>
<xsd:attribute name="seq" type="xsd:unsignedShort" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_WordprocessingShape">
<xsd:sequence minOccurs="1" maxOccurs="1">
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="0" maxOccurs="1"/>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1"
maxOccurs="1"/>
<xsd:element name="cNvCnPr" type="a:CT_NonVisualConnectorProperties" minOccurs="1"
maxOccurs="1"/>
</xsd:choice>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element name="txbx" type="CT_TextboxInfo" minOccurs="1" maxOccurs="1"/>
<xsd:element name="linkedTxbx" type="CT_LinkedTextboxInformation" minOccurs="1"
maxOccurs="1"/>
</xsd:choice>
<xsd:element name="bodyPr" type="a:CT_TextBodyProperties" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="normalEastAsianFlow" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_GraphicFrame">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvFrPr" type="a:CT_NonVisualGraphicFrameProperties" minOccurs="1"
maxOccurs="1"/>
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="1" maxOccurs="1"/>
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_WordprocessingContentPartNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="0" maxOccurs="1"/>
<xsd:element name="cNvContentPartPr" type="a:CT_NonVisualContentPartProperties" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_WordprocessingContentPart">
<xsd:sequence>
<xsd:element name="nvContentPartPr" type="CT_WordprocessingContentPartNonVisual" minOccurs="0" maxOccurs="1"/>
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="0" maxOccurs="1"/>
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="bwMode" type="a:ST_BlackWhiteMode" use="optional"/>
<xsd:attribute ref="r:id" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_WordprocessingGroup">
<xsd:sequence minOccurs="1" maxOccurs="1">
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="0" maxOccurs="1"/>
<xsd:element name="cNvGrpSpPr" type="a:CT_NonVisualGroupDrawingShapeProps" minOccurs="1"
maxOccurs="1"/>
<xsd:element name="grpSpPr" type="a:CT_GroupShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="wsp"/>
<xsd:element name="grpSp" type="CT_WordprocessingGroup"/>
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
<xsd:element ref="dpct:pic"/>
<xsd:element name="contentPart" type="CT_WordprocessingContentPart"/>
</xsd:choice>
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_WordprocessingCanvas">
<xsd:sequence minOccurs="1" maxOccurs="1">
<xsd:element name="bg" type="a:CT_BackgroundFormatting" minOccurs="0" maxOccurs="1"/>
<xsd:element name="whole" type="a:CT_WholeE2oFormatting" minOccurs="0" maxOccurs="1"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="wsp"/>
<xsd:element ref="dpct:pic"/>
<xsd:element name="contentPart" type="CT_WordprocessingContentPart"/>
<xsd:element ref="wgp"/>
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
</xsd:choice>
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="wpc" type="CT_WordprocessingCanvas"/>
<xsd:element name="wgp" type="CT_WordprocessingGroup"/>
<xsd:element name="wsp" type="CT_WordprocessingShape"/>
<xsd:element name="inline" type="CT_Inline"/>
<xsd:element name="anchor" type="CT_Anchor"/>
</xsd:schema>
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/characteristics"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/characteristics"
elementFormDefault="qualified">
<xsd:complexType name="CT_AdditionalCharacteristics">
<xsd:sequence>
<xsd:element name="characteristic" type="CT_Characteristic" minOccurs="0"
maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Characteristic">
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="relation" type="ST_Relation" use="required"/>
<xsd:attribute name="val" type="xsd:string" use="required"/>
<xsd:attribute name="vocabulary" type="xsd:anyURI" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="ST_Relation">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="ge"/>
<xsd:enumeration value="le"/>
<xsd:enumeration value="gt"/>
<xsd:enumeration value="lt"/>
<xsd:enumeration value="eq"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:element name="additionalCharacteristics" type="CT_AdditionalCharacteristics"/>
</xsd:schema>
@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/bibliography"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/bibliography"
elementFormDefault="qualified">
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:simpleType name="ST_SourceType">
<xsd:restriction base="s:ST_String">
<xsd:enumeration value="ArticleInAPeriodical"/>
<xsd:enumeration value="Book"/>
<xsd:enumeration value="BookSection"/>
<xsd:enumeration value="JournalArticle"/>
<xsd:enumeration value="ConferenceProceedings"/>
<xsd:enumeration value="Report"/>
<xsd:enumeration value="SoundRecording"/>
<xsd:enumeration value="Performance"/>
<xsd:enumeration value="Art"/>
<xsd:enumeration value="DocumentFromInternetSite"/>
<xsd:enumeration value="InternetSite"/>
<xsd:enumeration value="Film"/>
<xsd:enumeration value="Interview"/>
<xsd:enumeration value="Patent"/>
<xsd:enumeration value="ElectronicSource"/>
<xsd:enumeration value="Case"/>
<xsd:enumeration value="Misc"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_NameListType">
<xsd:sequence>
<xsd:element name="Person" type="CT_PersonType" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_PersonType">
<xsd:sequence>
<xsd:element name="Last" type="s:ST_String" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="First" type="s:ST_String" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="Middle" type="s:ST_String" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_NameType">
<xsd:sequence>
<xsd:element name="NameList" type="CT_NameListType" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_NameOrCorporateType">
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element name="NameList" type="CT_NameListType" minOccurs="1" maxOccurs="1"/>
<xsd:element name="Corporate" minOccurs="1" maxOccurs="1" type="s:ST_String"/>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_AuthorType">
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="Artist" type="CT_NameType"/>
<xsd:element name="Author" type="CT_NameOrCorporateType"/>
<xsd:element name="BookAuthor" type="CT_NameType"/>
<xsd:element name="Compiler" type="CT_NameType"/>
<xsd:element name="Composer" type="CT_NameType"/>
<xsd:element name="Conductor" type="CT_NameType"/>
<xsd:element name="Counsel" type="CT_NameType"/>
<xsd:element name="Director" type="CT_NameType"/>
<xsd:element name="Editor" type="CT_NameType"/>
<xsd:element name="Interviewee" type="CT_NameType"/>
<xsd:element name="Interviewer" type="CT_NameType"/>
<xsd:element name="Inventor" type="CT_NameType"/>
<xsd:element name="Performer" type="CT_NameOrCorporateType"/>
<xsd:element name="ProducerName" type="CT_NameType"/>
<xsd:element name="Translator" type="CT_NameType"/>
<xsd:element name="Writer" type="CT_NameType"/>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SourceType">
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="AbbreviatedCaseNumber" type="s:ST_String"/>
<xsd:element name="AlbumTitle" type="s:ST_String"/>
<xsd:element name="Author" type="CT_AuthorType"/>
<xsd:element name="BookTitle" type="s:ST_String"/>
<xsd:element name="Broadcaster" type="s:ST_String"/>
<xsd:element name="BroadcastTitle" type="s:ST_String"/>
<xsd:element name="CaseNumber" type="s:ST_String"/>
<xsd:element name="ChapterNumber" type="s:ST_String"/>
<xsd:element name="City" type="s:ST_String"/>
<xsd:element name="Comments" type="s:ST_String"/>
<xsd:element name="ConferenceName" type="s:ST_String"/>
<xsd:element name="CountryRegion" type="s:ST_String"/>
<xsd:element name="Court" type="s:ST_String"/>
<xsd:element name="Day" type="s:ST_String"/>
<xsd:element name="DayAccessed" type="s:ST_String"/>
<xsd:element name="Department" type="s:ST_String"/>
<xsd:element name="Distributor" type="s:ST_String"/>
<xsd:element name="Edition" type="s:ST_String"/>
<xsd:element name="Guid" type="s:ST_String"/>
<xsd:element name="Institution" type="s:ST_String"/>
<xsd:element name="InternetSiteTitle" type="s:ST_String"/>
<xsd:element name="Issue" type="s:ST_String"/>
<xsd:element name="JournalName" type="s:ST_String"/>
<xsd:element name="LCID" type="s:ST_Lang"/>
<xsd:element name="Medium" type="s:ST_String"/>
<xsd:element name="Month" type="s:ST_String"/>
<xsd:element name="MonthAccessed" type="s:ST_String"/>
<xsd:element name="NumberVolumes" type="s:ST_String"/>
<xsd:element name="Pages" type="s:ST_String"/>
<xsd:element name="PatentNumber" type="s:ST_String"/>
<xsd:element name="PeriodicalTitle" type="s:ST_String"/>
<xsd:element name="ProductionCompany" type="s:ST_String"/>
<xsd:element name="PublicationTitle" type="s:ST_String"/>
<xsd:element name="Publisher" type="s:ST_String"/>
<xsd:element name="RecordingNumber" type="s:ST_String"/>
<xsd:element name="RefOrder" type="s:ST_String"/>
<xsd:element name="Reporter" type="s:ST_String"/>
<xsd:element name="SourceType" type="ST_SourceType"/>
<xsd:element name="ShortTitle" type="s:ST_String"/>
<xsd:element name="StandardNumber" type="s:ST_String"/>
<xsd:element name="StateProvince" type="s:ST_String"/>
<xsd:element name="Station" type="s:ST_String"/>
<xsd:element name="Tag" type="s:ST_String"/>
<xsd:element name="Theater" type="s:ST_String"/>
<xsd:element name="ThesisType" type="s:ST_String"/>
<xsd:element name="Title" type="s:ST_String"/>
<xsd:element name="Type" type="s:ST_String"/>
<xsd:element name="URL" type="s:ST_String"/>
<xsd:element name="Version" type="s:ST_String"/>
<xsd:element name="Volume" type="s:ST_String"/>
<xsd:element name="Year" type="s:ST_String"/>
<xsd:element name="YearAccessed" type="s:ST_String"/>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="Sources" type="CT_Sources"/>
<xsd:complexType name="CT_Sources">
<xsd:sequence>
<xsd:element name="Source" type="CT_SourceType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="SelectedStyle" type="s:ST_String"/>
<xsd:attribute name="StyleName" type="s:ST_String"/>
<xsd:attribute name="URI" type="s:ST_String"/>
</xsd:complexType>
</xsd:schema>
@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
elementFormDefault="qualified">
<xsd:simpleType name="ST_Lang">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_HexColorRGB">
<xsd:restriction base="xsd:hexBinary">
<xsd:length value="3" fixed="true"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_Panose">
<xsd:restriction base="xsd:hexBinary">
<xsd:length value="10"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_CalendarType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="gregorian"/>
<xsd:enumeration value="gregorianUs"/>
<xsd:enumeration value="gregorianMeFrench"/>
<xsd:enumeration value="gregorianArabic"/>
<xsd:enumeration value="hijri"/>
<xsd:enumeration value="hebrew"/>
<xsd:enumeration value="taiwan"/>
<xsd:enumeration value="japan"/>
<xsd:enumeration value="thai"/>
<xsd:enumeration value="korea"/>
<xsd:enumeration value="saka"/>
<xsd:enumeration value="gregorianXlitEnglish"/>
<xsd:enumeration value="gregorianXlitFrench"/>
<xsd:enumeration value="none"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_AlgClass">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="hash"/>
<xsd:enumeration value="custom"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_CryptProv">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="rsaAES"/>
<xsd:enumeration value="rsaFull"/>
<xsd:enumeration value="custom"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_AlgType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="typeAny"/>
<xsd:enumeration value="custom"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ColorType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_Guid">
<xsd:restriction base="xsd:token">
<xsd:pattern value="\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_OnOff">
<xsd:union memberTypes="xsd:boolean ST_OnOff1"/>
</xsd:simpleType>
<xsd:simpleType name="ST_OnOff1">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="on"/>
<xsd:enumeration value="off"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_String">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_XmlName">
<xsd:restriction base="xsd:NCName">
<xsd:minLength value="1"/>
<xsd:maxLength value="255"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_TrueFalse">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="t"/>
<xsd:enumeration value="f"/>
<xsd:enumeration value="true"/>
<xsd:enumeration value="false"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_TrueFalseBlank">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="t"/>
<xsd:enumeration value="f"/>
<xsd:enumeration value="true"/>
<xsd:enumeration value="false"/>
<xsd:enumeration value=""/>
<xsd:enumeration value="True"/>
<xsd:enumeration value="False"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_UnsignedDecimalNumber">
<xsd:restriction base="xsd:decimal">
<xsd:minInclusive value="0"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_TwipsMeasure">
<xsd:union memberTypes="ST_UnsignedDecimalNumber ST_PositiveUniversalMeasure"/>
</xsd:simpleType>
<xsd:simpleType name="ST_VerticalAlignRun">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="baseline"/>
<xsd:enumeration value="superscript"/>
<xsd:enumeration value="subscript"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_Xstring">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_XAlign">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="left"/>
<xsd:enumeration value="center"/>
<xsd:enumeration value="right"/>
<xsd:enumeration value="inside"/>
<xsd:enumeration value="outside"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_YAlign">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="inline"/>
<xsd:enumeration value="top"/>
<xsd:enumeration value="center"/>
<xsd:enumeration value="bottom"/>
<xsd:enumeration value="inside"/>
<xsd:enumeration value="outside"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ConformanceClass">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="strict"/>
<xsd:enumeration value="transitional"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_UniversalMeasure">
<xsd:restriction base="xsd:string">
<xsd:pattern value="-?[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_PositiveUniversalMeasure">
<xsd:restriction base="ST_UniversalMeasure">
<xsd:pattern value="[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_Percentage">
<xsd:restriction base="xsd:string">
<xsd:pattern value="-?[0-9]+(\.[0-9]+)?%"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_FixedPercentage">
<xsd:restriction base="ST_Percentage">
<xsd:pattern value="-?((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_PositivePercentage">
<xsd:restriction base="ST_Percentage">
<xsd:pattern value="[0-9]+(\.[0-9]+)?%"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_PositiveFixedPercentage">
<xsd:restriction base="ST_Percentage">
<xsd:pattern value="((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/customXml"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/customXml"
elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all">
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:complexType name="CT_DatastoreSchemaRef">
<xsd:attribute name="uri" type="xsd:string" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_DatastoreSchemaRefs">
<xsd:sequence>
<xsd:element name="schemaRef" type="CT_DatastoreSchemaRef" minOccurs="0" maxOccurs="unbounded"
/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_DatastoreItem">
<xsd:sequence>
<xsd:element name="schemaRefs" type="CT_DatastoreSchemaRefs" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="itemID" type="s:ST_Guid" use="required"/>
</xsd:complexType>
<xsd:element name="datastoreItem" type="CT_DatastoreItem"/>
</xsd:schema>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/schemaLibrary/2006/main"
targetNamespace="http://schemas.openxmlformats.org/schemaLibrary/2006/main"
attributeFormDefault="qualified" elementFormDefault="qualified">
<xsd:complexType name="CT_Schema">
<xsd:attribute name="uri" type="xsd:string" default=""/>
<xsd:attribute name="manifestLocation" type="xsd:string"/>
<xsd:attribute name="schemaLocation" type="xsd:string"/>
<xsd:attribute name="schemaLanguage" type="xsd:token"/>
</xsd:complexType>
<xsd:complexType name="CT_SchemaLibrary">
<xsd:sequence>
<xsd:element name="schema" type="CT_Schema" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="schemaLibrary" type="CT_SchemaLibrary"/>
</xsd:schema>
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"
blockDefault="#all" elementFormDefault="qualified">
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
schemaLocation="shared-documentPropertiesVariantTypes.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:element name="Properties" type="CT_Properties"/>
<xsd:complexType name="CT_Properties">
<xsd:sequence>
<xsd:element name="property" minOccurs="0" maxOccurs="unbounded" type="CT_Property"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Property">
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element ref="vt:vector"/>
<xsd:element ref="vt:array"/>
<xsd:element ref="vt:blob"/>
<xsd:element ref="vt:oblob"/>
<xsd:element ref="vt:empty"/>
<xsd:element ref="vt:null"/>
<xsd:element ref="vt:i1"/>
<xsd:element ref="vt:i2"/>
<xsd:element ref="vt:i4"/>
<xsd:element ref="vt:i8"/>
<xsd:element ref="vt:int"/>
<xsd:element ref="vt:ui1"/>
<xsd:element ref="vt:ui2"/>
<xsd:element ref="vt:ui4"/>
<xsd:element ref="vt:ui8"/>
<xsd:element ref="vt:uint"/>
<xsd:element ref="vt:r4"/>
<xsd:element ref="vt:r8"/>
<xsd:element ref="vt:decimal"/>
<xsd:element ref="vt:lpstr"/>
<xsd:element ref="vt:lpwstr"/>
<xsd:element ref="vt:bstr"/>
<xsd:element ref="vt:date"/>
<xsd:element ref="vt:filetime"/>
<xsd:element ref="vt:bool"/>
<xsd:element ref="vt:cy"/>
<xsd:element ref="vt:error"/>
<xsd:element ref="vt:stream"/>
<xsd:element ref="vt:ostream"/>
<xsd:element ref="vt:storage"/>
<xsd:element ref="vt:ostorage"/>
<xsd:element ref="vt:vstream"/>
<xsd:element ref="vt:clsid"/>
</xsd:choice>
<xsd:attribute name="fmtid" use="required" type="s:ST_Guid"/>
<xsd:attribute name="pid" use="required" type="xsd:int"/>
<xsd:attribute name="name" use="optional" type="xsd:string"/>
<xsd:attribute name="linkTarget" use="optional" type="xsd:string"/>
</xsd:complexType>
</xsd:schema>
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
elementFormDefault="qualified" blockDefault="#all">
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
schemaLocation="shared-documentPropertiesVariantTypes.xsd"/>
<xsd:element name="Properties" type="CT_Properties"/>
<xsd:complexType name="CT_Properties">
<xsd:all>
<xsd:element name="Template" minOccurs="0" maxOccurs="1" type="xsd:string"/>
<xsd:element name="Manager" minOccurs="0" maxOccurs="1" type="xsd:string"/>
<xsd:element name="Company" minOccurs="0" maxOccurs="1" type="xsd:string"/>
<xsd:element name="Pages" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="Words" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="Characters" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="PresentationFormat" minOccurs="0" maxOccurs="1" type="xsd:string"/>
<xsd:element name="Lines" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="Paragraphs" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="Slides" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="Notes" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="TotalTime" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="HiddenSlides" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="MMClips" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="ScaleCrop" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
<xsd:element name="HeadingPairs" minOccurs="0" maxOccurs="1" type="CT_VectorVariant"/>
<xsd:element name="TitlesOfParts" minOccurs="0" maxOccurs="1" type="CT_VectorLpstr"/>
<xsd:element name="LinksUpToDate" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
<xsd:element name="CharactersWithSpaces" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="SharedDoc" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
<xsd:element name="HyperlinkBase" minOccurs="0" maxOccurs="1" type="xsd:string"/>
<xsd:element name="HLinks" minOccurs="0" maxOccurs="1" type="CT_VectorVariant"/>
<xsd:element name="HyperlinksChanged" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
<xsd:element name="DigSig" minOccurs="0" maxOccurs="1" type="CT_DigSigBlob"/>
<xsd:element name="Application" minOccurs="0" maxOccurs="1" type="xsd:string"/>
<xsd:element name="AppVersion" minOccurs="0" maxOccurs="1" type="xsd:string"/>
<xsd:element name="DocSecurity" minOccurs="0" maxOccurs="1" type="xsd:int"/>
</xsd:all>
</xsd:complexType>
<xsd:complexType name="CT_VectorVariant">
<xsd:sequence minOccurs="1" maxOccurs="1">
<xsd:element ref="vt:vector"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_VectorLpstr">
<xsd:sequence minOccurs="1" maxOccurs="1">
<xsd:element ref="vt:vector"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_DigSigBlob">
<xsd:sequence minOccurs="1" maxOccurs="1">
<xsd:element ref="vt:blob"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
@@ -0,0 +1,195 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
blockDefault="#all" elementFormDefault="qualified">
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:simpleType name="ST_VectorBaseType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="variant"/>
<xsd:enumeration value="i1"/>
<xsd:enumeration value="i2"/>
<xsd:enumeration value="i4"/>
<xsd:enumeration value="i8"/>
<xsd:enumeration value="ui1"/>
<xsd:enumeration value="ui2"/>
<xsd:enumeration value="ui4"/>
<xsd:enumeration value="ui8"/>
<xsd:enumeration value="r4"/>
<xsd:enumeration value="r8"/>
<xsd:enumeration value="lpstr"/>
<xsd:enumeration value="lpwstr"/>
<xsd:enumeration value="bstr"/>
<xsd:enumeration value="date"/>
<xsd:enumeration value="filetime"/>
<xsd:enumeration value="bool"/>
<xsd:enumeration value="cy"/>
<xsd:enumeration value="error"/>
<xsd:enumeration value="clsid"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ArrayBaseType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="variant"/>
<xsd:enumeration value="i1"/>
<xsd:enumeration value="i2"/>
<xsd:enumeration value="i4"/>
<xsd:enumeration value="int"/>
<xsd:enumeration value="ui1"/>
<xsd:enumeration value="ui2"/>
<xsd:enumeration value="ui4"/>
<xsd:enumeration value="uint"/>
<xsd:enumeration value="r4"/>
<xsd:enumeration value="r8"/>
<xsd:enumeration value="decimal"/>
<xsd:enumeration value="bstr"/>
<xsd:enumeration value="date"/>
<xsd:enumeration value="bool"/>
<xsd:enumeration value="cy"/>
<xsd:enumeration value="error"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_Cy">
<xsd:restriction base="xsd:string">
<xsd:pattern value="\s*[0-9]*\.[0-9]{4}\s*"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_Error">
<xsd:restriction base="xsd:string">
<xsd:pattern value="\s*0x[0-9A-Za-z]{8}\s*"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Empty"/>
<xsd:complexType name="CT_Null"/>
<xsd:complexType name="CT_Vector">
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element ref="variant"/>
<xsd:element ref="i1"/>
<xsd:element ref="i2"/>
<xsd:element ref="i4"/>
<xsd:element ref="i8"/>
<xsd:element ref="ui1"/>
<xsd:element ref="ui2"/>
<xsd:element ref="ui4"/>
<xsd:element ref="ui8"/>
<xsd:element ref="r4"/>
<xsd:element ref="r8"/>
<xsd:element ref="lpstr"/>
<xsd:element ref="lpwstr"/>
<xsd:element ref="bstr"/>
<xsd:element ref="date"/>
<xsd:element ref="filetime"/>
<xsd:element ref="bool"/>
<xsd:element ref="cy"/>
<xsd:element ref="error"/>
<xsd:element ref="clsid"/>
</xsd:choice>
<xsd:attribute name="baseType" type="ST_VectorBaseType" use="required"/>
<xsd:attribute name="size" type="xsd:unsignedInt" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_Array">
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element ref="variant"/>
<xsd:element ref="i1"/>
<xsd:element ref="i2"/>
<xsd:element ref="i4"/>
<xsd:element ref="int"/>
<xsd:element ref="ui1"/>
<xsd:element ref="ui2"/>
<xsd:element ref="ui4"/>
<xsd:element ref="uint"/>
<xsd:element ref="r4"/>
<xsd:element ref="r8"/>
<xsd:element ref="decimal"/>
<xsd:element ref="bstr"/>
<xsd:element ref="date"/>
<xsd:element ref="bool"/>
<xsd:element ref="error"/>
<xsd:element ref="cy"/>
</xsd:choice>
<xsd:attribute name="lBounds" type="xsd:int" use="required"/>
<xsd:attribute name="uBounds" type="xsd:int" use="required"/>
<xsd:attribute name="baseType" type="ST_ArrayBaseType" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_Variant">
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element ref="variant"/>
<xsd:element ref="vector"/>
<xsd:element ref="array"/>
<xsd:element ref="blob"/>
<xsd:element ref="oblob"/>
<xsd:element ref="empty"/>
<xsd:element ref="null"/>
<xsd:element ref="i1"/>
<xsd:element ref="i2"/>
<xsd:element ref="i4"/>
<xsd:element ref="i8"/>
<xsd:element ref="int"/>
<xsd:element ref="ui1"/>
<xsd:element ref="ui2"/>
<xsd:element ref="ui4"/>
<xsd:element ref="ui8"/>
<xsd:element ref="uint"/>
<xsd:element ref="r4"/>
<xsd:element ref="r8"/>
<xsd:element ref="decimal"/>
<xsd:element ref="lpstr"/>
<xsd:element ref="lpwstr"/>
<xsd:element ref="bstr"/>
<xsd:element ref="date"/>
<xsd:element ref="filetime"/>
<xsd:element ref="bool"/>
<xsd:element ref="cy"/>
<xsd:element ref="error"/>
<xsd:element ref="stream"/>
<xsd:element ref="ostream"/>
<xsd:element ref="storage"/>
<xsd:element ref="ostorage"/>
<xsd:element ref="vstream"/>
<xsd:element ref="clsid"/>
</xsd:choice>
</xsd:complexType>
<xsd:complexType name="CT_Vstream">
<xsd:simpleContent>
<xsd:extension base="xsd:base64Binary">
<xsd:attribute name="version" type="s:ST_Guid"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:element name="variant" type="CT_Variant"/>
<xsd:element name="vector" type="CT_Vector"/>
<xsd:element name="array" type="CT_Array"/>
<xsd:element name="blob" type="xsd:base64Binary"/>
<xsd:element name="oblob" type="xsd:base64Binary"/>
<xsd:element name="empty" type="CT_Empty"/>
<xsd:element name="null" type="CT_Null"/>
<xsd:element name="i1" type="xsd:byte"/>
<xsd:element name="i2" type="xsd:short"/>
<xsd:element name="i4" type="xsd:int"/>
<xsd:element name="i8" type="xsd:long"/>
<xsd:element name="int" type="xsd:int"/>
<xsd:element name="ui1" type="xsd:unsignedByte"/>
<xsd:element name="ui2" type="xsd:unsignedShort"/>
<xsd:element name="ui4" type="xsd:unsignedInt"/>
<xsd:element name="ui8" type="xsd:unsignedLong"/>
<xsd:element name="uint" type="xsd:unsignedInt"/>
<xsd:element name="r4" type="xsd:float"/>
<xsd:element name="r8" type="xsd:double"/>
<xsd:element name="decimal" type="xsd:decimal"/>
<xsd:element name="lpstr" type="xsd:string"/>
<xsd:element name="lpwstr" type="xsd:string"/>
<xsd:element name="bstr" type="xsd:string"/>
<xsd:element name="date" type="xsd:dateTime"/>
<xsd:element name="filetime" type="xsd:dateTime"/>
<xsd:element name="bool" type="xsd:boolean"/>
<xsd:element name="cy" type="ST_Cy"/>
<xsd:element name="error" type="ST_Error"/>
<xsd:element name="stream" type="xsd:base64Binary"/>
<xsd:element name="ostream" type="xsd:base64Binary"/>
<xsd:element name="storage" type="xsd:base64Binary"/>
<xsd:element name="ostorage" type="xsd:base64Binary"/>
<xsd:element name="vstream" type="CT_Vstream"/>
<xsd:element name="clsid" type="s:ST_Guid"/>
</xsd:schema>
@@ -0,0 +1,582 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/math">
<xsd:import namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
schemaLocation="wml.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd"/>
<xsd:simpleType name="ST_Integer255">
<xsd:restriction base="xsd:integer">
<xsd:minInclusive value="1"/>
<xsd:maxInclusive value="255"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Integer255">
<xsd:attribute name="val" type="ST_Integer255" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_Integer2">
<xsd:restriction base="xsd:integer">
<xsd:minInclusive value="-2"/>
<xsd:maxInclusive value="2"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Integer2">
<xsd:attribute name="val" type="ST_Integer2" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_SpacingRule">
<xsd:restriction base="xsd:integer">
<xsd:minInclusive value="0"/>
<xsd:maxInclusive value="4"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_SpacingRule">
<xsd:attribute name="val" type="ST_SpacingRule" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_UnSignedInteger">
<xsd:restriction base="xsd:unsignedInt"/>
</xsd:simpleType>
<xsd:complexType name="CT_UnSignedInteger">
<xsd:attribute name="val" type="ST_UnSignedInteger" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_Char">
<xsd:restriction base="xsd:string">
<xsd:maxLength value="1"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Char">
<xsd:attribute name="val" type="ST_Char" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_OnOff">
<xsd:attribute name="val" type="s:ST_OnOff"/>
</xsd:complexType>
<xsd:complexType name="CT_String">
<xsd:attribute name="val" type="s:ST_String"/>
</xsd:complexType>
<xsd:complexType name="CT_XAlign">
<xsd:attribute name="val" type="s:ST_XAlign" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_YAlign">
<xsd:attribute name="val" type="s:ST_YAlign" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_Shp">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="centered"/>
<xsd:enumeration value="match"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Shp">
<xsd:attribute name="val" type="ST_Shp" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_FType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="bar"/>
<xsd:enumeration value="skw"/>
<xsd:enumeration value="lin"/>
<xsd:enumeration value="noBar"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_FType">
<xsd:attribute name="val" type="ST_FType" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_LimLoc">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="undOvr"/>
<xsd:enumeration value="subSup"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_LimLoc">
<xsd:attribute name="val" type="ST_LimLoc" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_TopBot">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="top"/>
<xsd:enumeration value="bot"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_TopBot">
<xsd:attribute name="val" type="ST_TopBot" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_Script">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="roman"/>
<xsd:enumeration value="script"/>
<xsd:enumeration value="fraktur"/>
<xsd:enumeration value="double-struck"/>
<xsd:enumeration value="sans-serif"/>
<xsd:enumeration value="monospace"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Script">
<xsd:attribute name="val" type="ST_Script"/>
</xsd:complexType>
<xsd:simpleType name="ST_Style">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="p"/>
<xsd:enumeration value="b"/>
<xsd:enumeration value="i"/>
<xsd:enumeration value="bi"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Style">
<xsd:attribute name="val" type="ST_Style"/>
</xsd:complexType>
<xsd:complexType name="CT_ManualBreak">
<xsd:attribute name="alnAt" type="ST_Integer255"/>
</xsd:complexType>
<xsd:group name="EG_ScriptStyle">
<xsd:sequence>
<xsd:element name="scr" minOccurs="0" type="CT_Script"/>
<xsd:element name="sty" minOccurs="0" type="CT_Style"/>
</xsd:sequence>
</xsd:group>
<xsd:complexType name="CT_RPR">
<xsd:sequence>
<xsd:element name="lit" minOccurs="0" type="CT_OnOff"/>
<xsd:choice>
<xsd:element name="nor" minOccurs="0" type="CT_OnOff"/>
<xsd:sequence>
<xsd:group ref="EG_ScriptStyle"/>
</xsd:sequence>
</xsd:choice>
<xsd:element name="brk" minOccurs="0" type="CT_ManualBreak"/>
<xsd:element name="aln" minOccurs="0" type="CT_OnOff"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Text">
<xsd:simpleContent>
<xsd:extension base="s:ST_String">
<xsd:attribute ref="xml:space" use="optional"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:complexType name="CT_R">
<xsd:sequence>
<xsd:element name="rPr" type="CT_RPR" minOccurs="0"/>
<xsd:group ref="w:EG_RPr" minOccurs="0"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:group ref="w:EG_RunInnerContent"/>
<xsd:element name="t" type="CT_Text" minOccurs="0"/>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_CtrlPr">
<xsd:sequence>
<xsd:group ref="w:EG_RPrMath" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_AccPr">
<xsd:sequence>
<xsd:element name="chr" type="CT_Char" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Acc">
<xsd:sequence>
<xsd:element name="accPr" type="CT_AccPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_BarPr">
<xsd:sequence>
<xsd:element name="pos" type="CT_TopBot" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Bar">
<xsd:sequence>
<xsd:element name="barPr" type="CT_BarPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_BoxPr">
<xsd:sequence>
<xsd:element name="opEmu" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="noBreak" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="diff" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="brk" type="CT_ManualBreak" minOccurs="0"/>
<xsd:element name="aln" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Box">
<xsd:sequence>
<xsd:element name="boxPr" type="CT_BoxPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_BorderBoxPr">
<xsd:sequence>
<xsd:element name="hideTop" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="hideBot" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="hideLeft" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="hideRight" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="strikeH" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="strikeV" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="strikeBLTR" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="strikeTLBR" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_BorderBox">
<xsd:sequence>
<xsd:element name="borderBoxPr" type="CT_BorderBoxPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_DPr">
<xsd:sequence>
<xsd:element name="begChr" type="CT_Char" minOccurs="0"/>
<xsd:element name="sepChr" type="CT_Char" minOccurs="0"/>
<xsd:element name="endChr" type="CT_Char" minOccurs="0"/>
<xsd:element name="grow" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="shp" type="CT_Shp" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_D">
<xsd:sequence>
<xsd:element name="dPr" type="CT_DPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_EqArrPr">
<xsd:sequence>
<xsd:element name="baseJc" type="CT_YAlign" minOccurs="0"/>
<xsd:element name="maxDist" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="objDist" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="rSpRule" type="CT_SpacingRule" minOccurs="0"/>
<xsd:element name="rSp" type="CT_UnSignedInteger" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_EqArr">
<xsd:sequence>
<xsd:element name="eqArrPr" type="CT_EqArrPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_FPr">
<xsd:sequence>
<xsd:element name="type" type="CT_FType" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_F">
<xsd:sequence>
<xsd:element name="fPr" type="CT_FPr" minOccurs="0"/>
<xsd:element name="num" type="CT_OMathArg"/>
<xsd:element name="den" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_FuncPr">
<xsd:sequence>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Func">
<xsd:sequence>
<xsd:element name="funcPr" type="CT_FuncPr" minOccurs="0"/>
<xsd:element name="fName" type="CT_OMathArg"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GroupChrPr">
<xsd:sequence>
<xsd:element name="chr" type="CT_Char" minOccurs="0"/>
<xsd:element name="pos" type="CT_TopBot" minOccurs="0"/>
<xsd:element name="vertJc" type="CT_TopBot" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GroupChr">
<xsd:sequence>
<xsd:element name="groupChrPr" type="CT_GroupChrPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_LimLowPr">
<xsd:sequence>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_LimLow">
<xsd:sequence>
<xsd:element name="limLowPr" type="CT_LimLowPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
<xsd:element name="lim" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_LimUppPr">
<xsd:sequence>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_LimUpp">
<xsd:sequence>
<xsd:element name="limUppPr" type="CT_LimUppPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
<xsd:element name="lim" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_MCPr">
<xsd:sequence>
<xsd:element name="count" type="CT_Integer255" minOccurs="0"/>
<xsd:element name="mcJc" type="CT_XAlign" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_MC">
<xsd:sequence>
<xsd:element name="mcPr" type="CT_MCPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_MCS">
<xsd:sequence>
<xsd:element name="mc" type="CT_MC" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_MPr">
<xsd:sequence>
<xsd:element name="baseJc" type="CT_YAlign" minOccurs="0"/>
<xsd:element name="plcHide" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="rSpRule" type="CT_SpacingRule" minOccurs="0"/>
<xsd:element name="cGpRule" type="CT_SpacingRule" minOccurs="0"/>
<xsd:element name="rSp" type="CT_UnSignedInteger" minOccurs="0"/>
<xsd:element name="cSp" type="CT_UnSignedInteger" minOccurs="0"/>
<xsd:element name="cGp" type="CT_UnSignedInteger" minOccurs="0"/>
<xsd:element name="mcs" type="CT_MCS" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_MR">
<xsd:sequence>
<xsd:element name="e" type="CT_OMathArg" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_M">
<xsd:sequence>
<xsd:element name="mPr" type="CT_MPr" minOccurs="0"/>
<xsd:element name="mr" type="CT_MR" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_NaryPr">
<xsd:sequence>
<xsd:element name="chr" type="CT_Char" minOccurs="0"/>
<xsd:element name="limLoc" type="CT_LimLoc" minOccurs="0"/>
<xsd:element name="grow" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="subHide" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="supHide" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Nary">
<xsd:sequence>
<xsd:element name="naryPr" type="CT_NaryPr" minOccurs="0"/>
<xsd:element name="sub" type="CT_OMathArg"/>
<xsd:element name="sup" type="CT_OMathArg"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_PhantPr">
<xsd:sequence>
<xsd:element name="show" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="zeroWid" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="zeroAsc" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="zeroDesc" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="transp" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Phant">
<xsd:sequence>
<xsd:element name="phantPr" type="CT_PhantPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_RadPr">
<xsd:sequence>
<xsd:element name="degHide" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Rad">
<xsd:sequence>
<xsd:element name="radPr" type="CT_RadPr" minOccurs="0"/>
<xsd:element name="deg" type="CT_OMathArg"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SPrePr">
<xsd:sequence>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SPre">
<xsd:sequence>
<xsd:element name="sPrePr" type="CT_SPrePr" minOccurs="0"/>
<xsd:element name="sub" type="CT_OMathArg"/>
<xsd:element name="sup" type="CT_OMathArg"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SSubPr">
<xsd:sequence>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SSub">
<xsd:sequence>
<xsd:element name="sSubPr" type="CT_SSubPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
<xsd:element name="sub" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SSubSupPr">
<xsd:sequence>
<xsd:element name="alnScr" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SSubSup">
<xsd:sequence>
<xsd:element name="sSubSupPr" type="CT_SSubSupPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
<xsd:element name="sub" type="CT_OMathArg"/>
<xsd:element name="sup" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SSupPr">
<xsd:sequence>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SSup">
<xsd:sequence>
<xsd:element name="sSupPr" type="CT_SSupPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
<xsd:element name="sup" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:group name="EG_OMathMathElements">
<xsd:choice>
<xsd:element name="acc" type="CT_Acc"/>
<xsd:element name="bar" type="CT_Bar"/>
<xsd:element name="box" type="CT_Box"/>
<xsd:element name="borderBox" type="CT_BorderBox"/>
<xsd:element name="d" type="CT_D"/>
<xsd:element name="eqArr" type="CT_EqArr"/>
<xsd:element name="f" type="CT_F"/>
<xsd:element name="func" type="CT_Func"/>
<xsd:element name="groupChr" type="CT_GroupChr"/>
<xsd:element name="limLow" type="CT_LimLow"/>
<xsd:element name="limUpp" type="CT_LimUpp"/>
<xsd:element name="m" type="CT_M"/>
<xsd:element name="nary" type="CT_Nary"/>
<xsd:element name="phant" type="CT_Phant"/>
<xsd:element name="rad" type="CT_Rad"/>
<xsd:element name="sPre" type="CT_SPre"/>
<xsd:element name="sSub" type="CT_SSub"/>
<xsd:element name="sSubSup" type="CT_SSubSup"/>
<xsd:element name="sSup" type="CT_SSup"/>
<xsd:element name="r" type="CT_R"/>
</xsd:choice>
</xsd:group>
<xsd:group name="EG_OMathElements">
<xsd:choice>
<xsd:group ref="EG_OMathMathElements"/>
<xsd:group ref="w:EG_PContentMath"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_OMathArgPr">
<xsd:sequence>
<xsd:element name="argSz" type="CT_Integer2" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_OMathArg">
<xsd:sequence>
<xsd:element name="argPr" type="CT_OMathArgPr" minOccurs="0"/>
<xsd:group ref="EG_OMathElements" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="ST_Jc">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="left"/>
<xsd:enumeration value="right"/>
<xsd:enumeration value="center"/>
<xsd:enumeration value="centerGroup"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_OMathJc">
<xsd:attribute name="val" type="ST_Jc"/>
</xsd:complexType>
<xsd:complexType name="CT_OMathParaPr">
<xsd:sequence>
<xsd:element name="jc" type="CT_OMathJc" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_TwipsMeasure">
<xsd:attribute name="val" type="s:ST_TwipsMeasure" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_BreakBin">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="before"/>
<xsd:enumeration value="after"/>
<xsd:enumeration value="repeat"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_BreakBin">
<xsd:attribute name="val" type="ST_BreakBin"/>
</xsd:complexType>
<xsd:simpleType name="ST_BreakBinSub">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="--"/>
<xsd:enumeration value="-+"/>
<xsd:enumeration value="+-"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_BreakBinSub">
<xsd:attribute name="val" type="ST_BreakBinSub"/>
</xsd:complexType>
<xsd:complexType name="CT_MathPr">
<xsd:sequence>
<xsd:element name="mathFont" type="CT_String" minOccurs="0"/>
<xsd:element name="brkBin" type="CT_BreakBin" minOccurs="0"/>
<xsd:element name="brkBinSub" type="CT_BreakBinSub" minOccurs="0"/>
<xsd:element name="smallFrac" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="dispDef" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="lMargin" type="CT_TwipsMeasure" minOccurs="0"/>
<xsd:element name="rMargin" type="CT_TwipsMeasure" minOccurs="0"/>
<xsd:element name="defJc" type="CT_OMathJc" minOccurs="0"/>
<xsd:element name="preSp" type="CT_TwipsMeasure" minOccurs="0"/>
<xsd:element name="postSp" type="CT_TwipsMeasure" minOccurs="0"/>
<xsd:element name="interSp" type="CT_TwipsMeasure" minOccurs="0"/>
<xsd:element name="intraSp" type="CT_TwipsMeasure" minOccurs="0"/>
<xsd:choice minOccurs="0">
<xsd:element name="wrapIndent" type="CT_TwipsMeasure"/>
<xsd:element name="wrapRight" type="CT_OnOff"/>
</xsd:choice>
<xsd:element name="intLim" type="CT_LimLoc" minOccurs="0"/>
<xsd:element name="naryLim" type="CT_LimLoc" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="mathPr" type="CT_MathPr"/>
<xsd:complexType name="CT_OMathPara">
<xsd:sequence>
<xsd:element name="oMathParaPr" type="CT_OMathParaPr" minOccurs="0"/>
<xsd:element name="oMath" type="CT_OMath" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_OMath">
<xsd:sequence>
<xsd:group ref="EG_OMathElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="oMathPara" type="CT_OMathPara"/>
<xsd:element name="oMath" type="CT_OMath"/>
</xsd:schema>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
elementFormDefault="qualified"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
blockDefault="#all">
<xsd:simpleType name="ST_RelationshipId">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:attribute name="id" type="ST_RelationshipId"/>
<xsd:attribute name="embed" type="ST_RelationshipId"/>
<xsd:attribute name="link" type="ST_RelationshipId"/>
<xsd:attribute name="dm" type="ST_RelationshipId" default=""/>
<xsd:attribute name="lo" type="ST_RelationshipId" default=""/>
<xsd:attribute name="qs" type="ST_RelationshipId" default=""/>
<xsd:attribute name="cs" type="ST_RelationshipId" default=""/>
<xsd:attribute name="blip" type="ST_RelationshipId" default=""/>
<xsd:attribute name="pict" type="ST_RelationshipId"/>
<xsd:attribute name="href" type="ST_RelationshipId"/>
<xsd:attribute name="topLeft" type="ST_RelationshipId"/>
<xsd:attribute name="topRight" type="ST_RelationshipId"/>
<xsd:attribute name="bottomLeft" type="ST_RelationshipId"/>
<xsd:attribute name="bottomRight" type="ST_RelationshipId"/>
</xsd:schema>
@@ -0,0 +1,570 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:schemas-microsoft-com:vml"
xmlns:pvml="urn:schemas-microsoft-com:office:powerpoint"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="urn:schemas-microsoft-com:vml" elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="urn:schemas-microsoft-com:office:office"
schemaLocation="vml-officeDrawing.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
schemaLocation="wml.xsd"/>
<xsd:import namespace="urn:schemas-microsoft-com:office:word"
schemaLocation="vml-wordprocessingDrawing.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
schemaLocation="shared-relationshipReference.xsd"/>
<xsd:import namespace="urn:schemas-microsoft-com:office:excel"
schemaLocation="vml-spreadsheetDrawing.xsd"/>
<xsd:import namespace="urn:schemas-microsoft-com:office:powerpoint"
schemaLocation="vml-presentationDrawing.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:attributeGroup name="AG_Id">
<xsd:attribute name="id" type="xsd:string" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_Style">
<xsd:attribute name="style" type="xsd:string" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_Type">
<xsd:attribute name="type" type="xsd:string" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_Adj">
<xsd:attribute name="adj" type="xsd:string" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_Path">
<xsd:attribute name="path" type="xsd:string" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_Fill">
<xsd:attribute name="filled" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="fillcolor" type="s:ST_ColorType" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_Chromakey">
<xsd:attribute name="chromakey" type="s:ST_ColorType" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_Ext">
<xsd:attribute name="ext" form="qualified" type="ST_Ext"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_CoreAttributes">
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attributeGroup ref="AG_Style"/>
<xsd:attribute name="href" type="xsd:string" use="optional"/>
<xsd:attribute name="target" type="xsd:string" use="optional"/>
<xsd:attribute name="class" type="xsd:string" use="optional"/>
<xsd:attribute name="title" type="xsd:string" use="optional"/>
<xsd:attribute name="alt" type="xsd:string" use="optional"/>
<xsd:attribute name="coordsize" type="xsd:string" use="optional"/>
<xsd:attribute name="coordorigin" type="xsd:string" use="optional"/>
<xsd:attribute name="wrapcoords" type="xsd:string" use="optional"/>
<xsd:attribute name="print" type="s:ST_TrueFalse" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_ShapeAttributes">
<xsd:attributeGroup ref="AG_Chromakey"/>
<xsd:attributeGroup ref="AG_Fill"/>
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
<xsd:attribute name="stroked" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="strokecolor" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="strokeweight" type="xsd:string" use="optional"/>
<xsd:attribute name="insetpen" type="s:ST_TrueFalse" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_OfficeCoreAttributes">
<xsd:attribute ref="o:spid"/>
<xsd:attribute ref="o:oned"/>
<xsd:attribute ref="o:regroupid"/>
<xsd:attribute ref="o:doubleclicknotify"/>
<xsd:attribute ref="o:button"/>
<xsd:attribute ref="o:userhidden"/>
<xsd:attribute ref="o:bullet"/>
<xsd:attribute ref="o:hr"/>
<xsd:attribute ref="o:hrstd"/>
<xsd:attribute ref="o:hrnoshade"/>
<xsd:attribute ref="o:hrpct"/>
<xsd:attribute ref="o:hralign"/>
<xsd:attribute ref="o:allowincell"/>
<xsd:attribute ref="o:allowoverlap"/>
<xsd:attribute ref="o:userdrawn"/>
<xsd:attribute ref="o:bordertopcolor"/>
<xsd:attribute ref="o:borderleftcolor"/>
<xsd:attribute ref="o:borderbottomcolor"/>
<xsd:attribute ref="o:borderrightcolor"/>
<xsd:attribute ref="o:dgmlayout"/>
<xsd:attribute ref="o:dgmnodekind"/>
<xsd:attribute ref="o:dgmlayoutmru"/>
<xsd:attribute ref="o:insetmode"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_OfficeShapeAttributes">
<xsd:attribute ref="o:spt"/>
<xsd:attribute ref="o:connectortype"/>
<xsd:attribute ref="o:bwmode"/>
<xsd:attribute ref="o:bwpure"/>
<xsd:attribute ref="o:bwnormal"/>
<xsd:attribute ref="o:forcedash"/>
<xsd:attribute ref="o:oleicon"/>
<xsd:attribute ref="o:ole"/>
<xsd:attribute ref="o:preferrelative"/>
<xsd:attribute ref="o:cliptowrap"/>
<xsd:attribute ref="o:clip"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_AllCoreAttributes">
<xsd:attributeGroup ref="AG_CoreAttributes"/>
<xsd:attributeGroup ref="AG_OfficeCoreAttributes"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_AllShapeAttributes">
<xsd:attributeGroup ref="AG_ShapeAttributes"/>
<xsd:attributeGroup ref="AG_OfficeShapeAttributes"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_ImageAttributes">
<xsd:attribute name="src" type="xsd:string" use="optional"/>
<xsd:attribute name="cropleft" type="xsd:string" use="optional"/>
<xsd:attribute name="croptop" type="xsd:string" use="optional"/>
<xsd:attribute name="cropright" type="xsd:string" use="optional"/>
<xsd:attribute name="cropbottom" type="xsd:string" use="optional"/>
<xsd:attribute name="gain" type="xsd:string" use="optional"/>
<xsd:attribute name="blacklevel" type="xsd:string" use="optional"/>
<xsd:attribute name="gamma" type="xsd:string" use="optional"/>
<xsd:attribute name="grayscale" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="bilevel" type="s:ST_TrueFalse" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_StrokeAttributes">
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="weight" type="xsd:string" use="optional"/>
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
<xsd:attribute name="linestyle" type="ST_StrokeLineStyle" use="optional"/>
<xsd:attribute name="miterlimit" type="xsd:decimal" use="optional"/>
<xsd:attribute name="joinstyle" type="ST_StrokeJoinStyle" use="optional"/>
<xsd:attribute name="endcap" type="ST_StrokeEndCap" use="optional"/>
<xsd:attribute name="dashstyle" type="xsd:string" use="optional"/>
<xsd:attribute name="filltype" type="ST_FillType" use="optional"/>
<xsd:attribute name="src" type="xsd:string" use="optional"/>
<xsd:attribute name="imageaspect" type="ST_ImageAspect" use="optional"/>
<xsd:attribute name="imagesize" type="xsd:string" use="optional"/>
<xsd:attribute name="imagealignshape" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="startarrow" type="ST_StrokeArrowType" use="optional"/>
<xsd:attribute name="startarrowwidth" type="ST_StrokeArrowWidth" use="optional"/>
<xsd:attribute name="startarrowlength" type="ST_StrokeArrowLength" use="optional"/>
<xsd:attribute name="endarrow" type="ST_StrokeArrowType" use="optional"/>
<xsd:attribute name="endarrowwidth" type="ST_StrokeArrowWidth" use="optional"/>
<xsd:attribute name="endarrowlength" type="ST_StrokeArrowLength" use="optional"/>
<xsd:attribute ref="o:href"/>
<xsd:attribute ref="o:althref"/>
<xsd:attribute ref="o:title"/>
<xsd:attribute ref="o:forcedash"/>
<xsd:attribute ref="r:id" use="optional"/>
<xsd:attribute name="insetpen" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute ref="o:relid"/>
</xsd:attributeGroup>
<xsd:group name="EG_ShapeElements">
<xsd:choice>
<xsd:element ref="path"/>
<xsd:element ref="formulas"/>
<xsd:element ref="handles"/>
<xsd:element ref="fill"/>
<xsd:element ref="stroke"/>
<xsd:element ref="shadow"/>
<xsd:element ref="textbox"/>
<xsd:element ref="textpath"/>
<xsd:element ref="imagedata"/>
<xsd:element ref="o:skew"/>
<xsd:element ref="o:extrusion"/>
<xsd:element ref="o:callout"/>
<xsd:element ref="o:lock"/>
<xsd:element ref="o:clippath"/>
<xsd:element ref="o:signatureline"/>
<xsd:element ref="w10:wrap"/>
<xsd:element ref="w10:anchorlock"/>
<xsd:element ref="w10:bordertop"/>
<xsd:element ref="w10:borderbottom"/>
<xsd:element ref="w10:borderleft"/>
<xsd:element ref="w10:borderright"/>
<xsd:element ref="x:ClientData" minOccurs="0"/>
<xsd:element ref="pvml:textdata" minOccurs="0"/>
</xsd:choice>
</xsd:group>
<xsd:element name="shape" type="CT_Shape"/>
<xsd:element name="shapetype" type="CT_Shapetype"/>
<xsd:element name="group" type="CT_Group"/>
<xsd:element name="background" type="CT_Background"/>
<xsd:complexType name="CT_Shape">
<xsd:choice maxOccurs="unbounded">
<xsd:group ref="EG_ShapeElements"/>
<xsd:element ref="o:ink"/>
<xsd:element ref="pvml:iscomment"/>
<xsd:element ref="o:equationxml"/>
</xsd:choice>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attributeGroup ref="AG_Type"/>
<xsd:attributeGroup ref="AG_Adj"/>
<xsd:attributeGroup ref="AG_Path"/>
<xsd:attribute ref="o:gfxdata"/>
<xsd:attribute name="equationxml" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Shapetype">
<xsd:sequence>
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="o:complex" minOccurs="0"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attributeGroup ref="AG_Adj"/>
<xsd:attributeGroup ref="AG_Path"/>
<xsd:attribute ref="o:master"/>
</xsd:complexType>
<xsd:complexType name="CT_Group">
<xsd:choice maxOccurs="unbounded">
<xsd:group ref="EG_ShapeElements"/>
<xsd:element ref="group"/>
<xsd:element ref="shape"/>
<xsd:element ref="shapetype"/>
<xsd:element ref="arc"/>
<xsd:element ref="curve"/>
<xsd:element ref="image"/>
<xsd:element ref="line"/>
<xsd:element ref="oval"/>
<xsd:element ref="polyline"/>
<xsd:element ref="rect"/>
<xsd:element ref="roundrect"/>
<xsd:element ref="o:diagram"/>
</xsd:choice>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_Fill"/>
<xsd:attribute name="editas" type="ST_EditAs" use="optional"/>
<xsd:attribute ref="o:tableproperties"/>
<xsd:attribute ref="o:tablelimits"/>
</xsd:complexType>
<xsd:complexType name="CT_Background">
<xsd:sequence>
<xsd:element ref="fill" minOccurs="0"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attributeGroup ref="AG_Fill"/>
<xsd:attribute ref="o:bwmode"/>
<xsd:attribute ref="o:bwpure"/>
<xsd:attribute ref="o:bwnormal"/>
<xsd:attribute ref="o:targetscreensize"/>
</xsd:complexType>
<xsd:element name="fill" type="CT_Fill"/>
<xsd:element name="formulas" type="CT_Formulas"/>
<xsd:element name="handles" type="CT_Handles"/>
<xsd:element name="imagedata" type="CT_ImageData"/>
<xsd:element name="path" type="CT_Path"/>
<xsd:element name="textbox" type="CT_Textbox"/>
<xsd:element name="shadow" type="CT_Shadow"/>
<xsd:element name="stroke" type="CT_Stroke"/>
<xsd:element name="textpath" type="CT_TextPath"/>
<xsd:complexType name="CT_Fill">
<xsd:sequence>
<xsd:element ref="o:fill" minOccurs="0"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attribute name="type" type="ST_FillType" use="optional"/>
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="src" type="xsd:string" use="optional"/>
<xsd:attribute ref="o:href"/>
<xsd:attribute ref="o:althref"/>
<xsd:attribute name="size" type="xsd:string" use="optional"/>
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
<xsd:attribute name="position" type="xsd:string" use="optional"/>
<xsd:attribute name="aspect" type="ST_ImageAspect" use="optional"/>
<xsd:attribute name="colors" type="xsd:string" use="optional"/>
<xsd:attribute name="angle" type="xsd:decimal" use="optional"/>
<xsd:attribute name="alignshape" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="focus" type="xsd:string" use="optional"/>
<xsd:attribute name="focussize" type="xsd:string" use="optional"/>
<xsd:attribute name="focusposition" type="xsd:string" use="optional"/>
<xsd:attribute name="method" type="ST_FillMethod" use="optional"/>
<xsd:attribute ref="o:detectmouseclick"/>
<xsd:attribute ref="o:title"/>
<xsd:attribute ref="o:opacity2"/>
<xsd:attribute name="recolor" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="rotate" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute ref="r:id" use="optional"/>
<xsd:attribute ref="o:relid" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Formulas">
<xsd:sequence>
<xsd:element name="f" type="CT_F" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_F">
<xsd:attribute name="eqn" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="CT_Handles">
<xsd:sequence>
<xsd:element name="h" type="CT_H" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_H">
<xsd:attribute name="position" type="xsd:string"/>
<xsd:attribute name="polar" type="xsd:string"/>
<xsd:attribute name="map" type="xsd:string"/>
<xsd:attribute name="invx" type="s:ST_TrueFalse"/>
<xsd:attribute name="invy" type="s:ST_TrueFalse"/>
<xsd:attribute name="switch" type="s:ST_TrueFalseBlank"/>
<xsd:attribute name="xrange" type="xsd:string"/>
<xsd:attribute name="yrange" type="xsd:string"/>
<xsd:attribute name="radiusrange" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="CT_ImageData">
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attributeGroup ref="AG_ImageAttributes"/>
<xsd:attributeGroup ref="AG_Chromakey"/>
<xsd:attribute name="embosscolor" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="recolortarget" type="s:ST_ColorType"/>
<xsd:attribute ref="o:href"/>
<xsd:attribute ref="o:althref"/>
<xsd:attribute ref="o:title"/>
<xsd:attribute ref="o:oleid"/>
<xsd:attribute ref="o:detectmouseclick"/>
<xsd:attribute ref="o:movie"/>
<xsd:attribute ref="o:relid"/>
<xsd:attribute ref="r:id"/>
<xsd:attribute ref="r:pict"/>
<xsd:attribute ref="r:href"/>
</xsd:complexType>
<xsd:complexType name="CT_Path">
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attribute name="v" type="xsd:string" use="optional"/>
<xsd:attribute name="limo" type="xsd:string" use="optional"/>
<xsd:attribute name="textboxrect" type="xsd:string" use="optional"/>
<xsd:attribute name="fillok" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="strokeok" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="shadowok" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="arrowok" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="gradientshapeok" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="textpathok" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="insetpenok" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute ref="o:connecttype"/>
<xsd:attribute ref="o:connectlocs"/>
<xsd:attribute ref="o:connectangles"/>
<xsd:attribute ref="o:extrusionok"/>
</xsd:complexType>
<xsd:complexType name="CT_Shadow">
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="type" type="ST_ShadowType" use="optional"/>
<xsd:attribute name="obscured" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
<xsd:attribute name="offset" type="xsd:string" use="optional"/>
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="offset2" type="xsd:string" use="optional"/>
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
<xsd:attribute name="matrix" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Stroke">
<xsd:sequence>
<xsd:element ref="o:left" minOccurs="0"/>
<xsd:element ref="o:top" minOccurs="0"/>
<xsd:element ref="o:right" minOccurs="0"/>
<xsd:element ref="o:bottom" minOccurs="0"/>
<xsd:element ref="o:column" minOccurs="0"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attributeGroup ref="AG_StrokeAttributes"/>
</xsd:complexType>
<xsd:complexType name="CT_Textbox">
<xsd:choice>
<xsd:element ref="w:txbxContent" minOccurs="0"/>
<xsd:any namespace="##local" processContents="skip"/>
</xsd:choice>
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attributeGroup ref="AG_Style"/>
<xsd:attribute name="inset" type="xsd:string" use="optional"/>
<xsd:attribute ref="o:singleclick"/>
<xsd:attribute ref="o:insetmode"/>
</xsd:complexType>
<xsd:complexType name="CT_TextPath">
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attributeGroup ref="AG_Style"/>
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="fitshape" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="fitpath" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="trim" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="xscale" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="string" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:element name="arc" type="CT_Arc"/>
<xsd:element name="curve" type="CT_Curve"/>
<xsd:element name="image" type="CT_Image"/>
<xsd:element name="line" type="CT_Line"/>
<xsd:element name="oval" type="CT_Oval"/>
<xsd:element name="polyline" type="CT_PolyLine"/>
<xsd:element name="rect" type="CT_Rect"/>
<xsd:element name="roundrect" type="CT_RoundRect"/>
<xsd:complexType name="CT_Arc">
<xsd:sequence>
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attribute name="startAngle" type="xsd:decimal" use="optional"/>
<xsd:attribute name="endAngle" type="xsd:decimal" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Curve">
<xsd:sequence>
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attribute name="from" type="xsd:string" use="optional"/>
<xsd:attribute name="control1" type="xsd:string" use="optional"/>
<xsd:attribute name="control2" type="xsd:string" use="optional"/>
<xsd:attribute name="to" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Image">
<xsd:sequence>
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attributeGroup ref="AG_ImageAttributes"/>
</xsd:complexType>
<xsd:complexType name="CT_Line">
<xsd:sequence>
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attribute name="from" type="xsd:string" use="optional"/>
<xsd:attribute name="to" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Oval">
<xsd:choice maxOccurs="unbounded">
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:choice>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
</xsd:complexType>
<xsd:complexType name="CT_PolyLine">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:group ref="EG_ShapeElements"/>
<xsd:element ref="o:ink"/>
</xsd:choice>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attribute name="points" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Rect">
<xsd:choice maxOccurs="unbounded">
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:choice>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
</xsd:complexType>
<xsd:complexType name="CT_RoundRect">
<xsd:choice maxOccurs="unbounded">
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:choice>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attribute name="arcsize" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="ST_Ext">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="view"/>
<xsd:enumeration value="edit"/>
<xsd:enumeration value="backwardCompatible"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_FillType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="solid"/>
<xsd:enumeration value="gradient"/>
<xsd:enumeration value="gradientRadial"/>
<xsd:enumeration value="tile"/>
<xsd:enumeration value="pattern"/>
<xsd:enumeration value="frame"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_FillMethod">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none"/>
<xsd:enumeration value="linear"/>
<xsd:enumeration value="sigma"/>
<xsd:enumeration value="any"/>
<xsd:enumeration value="linear sigma"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ShadowType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="single"/>
<xsd:enumeration value="double"/>
<xsd:enumeration value="emboss"/>
<xsd:enumeration value="perspective"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_StrokeLineStyle">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="single"/>
<xsd:enumeration value="thinThin"/>
<xsd:enumeration value="thinThick"/>
<xsd:enumeration value="thickThin"/>
<xsd:enumeration value="thickBetweenThin"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_StrokeJoinStyle">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="round"/>
<xsd:enumeration value="bevel"/>
<xsd:enumeration value="miter"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_StrokeEndCap">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="flat"/>
<xsd:enumeration value="square"/>
<xsd:enumeration value="round"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_StrokeArrowLength">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="short"/>
<xsd:enumeration value="medium"/>
<xsd:enumeration value="long"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_StrokeArrowWidth">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="narrow"/>
<xsd:enumeration value="medium"/>
<xsd:enumeration value="wide"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_StrokeArrowType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none"/>
<xsd:enumeration value="block"/>
<xsd:enumeration value="classic"/>
<xsd:enumeration value="oval"/>
<xsd:enumeration value="diamond"/>
<xsd:enumeration value="open"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ImageAspect">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="ignore"/>
<xsd:enumeration value="atMost"/>
<xsd:enumeration value="atLeast"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_EditAs">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="canvas"/>
<xsd:enumeration value="orgchart"/>
<xsd:enumeration value="radial"/>
<xsd:enumeration value="cycle"/>
<xsd:enumeration value="stacked"/>
<xsd:enumeration value="venn"/>
<xsd:enumeration value="bullseye"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
@@ -0,0 +1,509 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="urn:schemas-microsoft-com:office:office" elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="urn:schemas-microsoft-com:vml" schemaLocation="vml-main.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
schemaLocation="shared-relationshipReference.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:attribute name="bwmode" type="ST_BWMode"/>
<xsd:attribute name="bwpure" type="ST_BWMode"/>
<xsd:attribute name="bwnormal" type="ST_BWMode"/>
<xsd:attribute name="targetscreensize" type="ST_ScreenSize"/>
<xsd:attribute name="insetmode" type="ST_InsetMode" default="custom"/>
<xsd:attribute name="spt" type="xsd:float"/>
<xsd:attribute name="wrapcoords" type="xsd:string"/>
<xsd:attribute name="oned" type="s:ST_TrueFalse"/>
<xsd:attribute name="regroupid" type="xsd:integer"/>
<xsd:attribute name="doubleclicknotify" type="s:ST_TrueFalse"/>
<xsd:attribute name="connectortype" type="ST_ConnectorType" default="straight"/>
<xsd:attribute name="button" type="s:ST_TrueFalse"/>
<xsd:attribute name="userhidden" type="s:ST_TrueFalse"/>
<xsd:attribute name="forcedash" type="s:ST_TrueFalse"/>
<xsd:attribute name="oleicon" type="s:ST_TrueFalse"/>
<xsd:attribute name="ole" type="s:ST_TrueFalseBlank"/>
<xsd:attribute name="preferrelative" type="s:ST_TrueFalse"/>
<xsd:attribute name="cliptowrap" type="s:ST_TrueFalse"/>
<xsd:attribute name="clip" type="s:ST_TrueFalse"/>
<xsd:attribute name="bullet" type="s:ST_TrueFalse"/>
<xsd:attribute name="hr" type="s:ST_TrueFalse"/>
<xsd:attribute name="hrstd" type="s:ST_TrueFalse"/>
<xsd:attribute name="hrnoshade" type="s:ST_TrueFalse"/>
<xsd:attribute name="hrpct" type="xsd:float"/>
<xsd:attribute name="hralign" type="ST_HrAlign" default="left"/>
<xsd:attribute name="allowincell" type="s:ST_TrueFalse"/>
<xsd:attribute name="allowoverlap" type="s:ST_TrueFalse"/>
<xsd:attribute name="userdrawn" type="s:ST_TrueFalse"/>
<xsd:attribute name="bordertopcolor" type="xsd:string"/>
<xsd:attribute name="borderleftcolor" type="xsd:string"/>
<xsd:attribute name="borderbottomcolor" type="xsd:string"/>
<xsd:attribute name="borderrightcolor" type="xsd:string"/>
<xsd:attribute name="connecttype" type="ST_ConnectType"/>
<xsd:attribute name="connectlocs" type="xsd:string"/>
<xsd:attribute name="connectangles" type="xsd:string"/>
<xsd:attribute name="master" type="xsd:string"/>
<xsd:attribute name="extrusionok" type="s:ST_TrueFalse"/>
<xsd:attribute name="href" type="xsd:string"/>
<xsd:attribute name="althref" type="xsd:string"/>
<xsd:attribute name="title" type="xsd:string"/>
<xsd:attribute name="singleclick" type="s:ST_TrueFalse"/>
<xsd:attribute name="oleid" type="xsd:float"/>
<xsd:attribute name="detectmouseclick" type="s:ST_TrueFalse"/>
<xsd:attribute name="movie" type="xsd:float"/>
<xsd:attribute name="spid" type="xsd:string"/>
<xsd:attribute name="opacity2" type="xsd:string"/>
<xsd:attribute name="relid" type="r:ST_RelationshipId"/>
<xsd:attribute name="dgmlayout" type="ST_DiagramLayout"/>
<xsd:attribute name="dgmnodekind" type="xsd:integer"/>
<xsd:attribute name="dgmlayoutmru" type="ST_DiagramLayout"/>
<xsd:attribute name="gfxdata" type="xsd:base64Binary"/>
<xsd:attribute name="tableproperties" type="xsd:string"/>
<xsd:attribute name="tablelimits" type="xsd:string"/>
<xsd:element name="shapedefaults" type="CT_ShapeDefaults"/>
<xsd:element name="shapelayout" type="CT_ShapeLayout"/>
<xsd:element name="signatureline" type="CT_SignatureLine"/>
<xsd:element name="ink" type="CT_Ink"/>
<xsd:element name="diagram" type="CT_Diagram"/>
<xsd:element name="equationxml" type="CT_EquationXml"/>
<xsd:complexType name="CT_ShapeDefaults">
<xsd:all minOccurs="0">
<xsd:element ref="v:fill" minOccurs="0"/>
<xsd:element ref="v:stroke" minOccurs="0"/>
<xsd:element ref="v:textbox" minOccurs="0"/>
<xsd:element ref="v:shadow" minOccurs="0"/>
<xsd:element ref="skew" minOccurs="0"/>
<xsd:element ref="extrusion" minOccurs="0"/>
<xsd:element ref="callout" minOccurs="0"/>
<xsd:element ref="lock" minOccurs="0"/>
<xsd:element name="colormru" minOccurs="0" type="CT_ColorMru"/>
<xsd:element name="colormenu" minOccurs="0" type="CT_ColorMenu"/>
</xsd:all>
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="spidmax" type="xsd:integer" use="optional"/>
<xsd:attribute name="style" type="xsd:string" use="optional"/>
<xsd:attribute name="fill" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="fillcolor" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="stroke" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="strokecolor" type="s:ST_ColorType"/>
<xsd:attribute name="allowincell" form="qualified" type="s:ST_TrueFalse"/>
</xsd:complexType>
<xsd:complexType name="CT_Ink">
<xsd:sequence/>
<xsd:attribute name="i" type="xsd:string"/>
<xsd:attribute name="annotation" type="s:ST_TrueFalse"/>
<xsd:attribute name="contentType" type="ST_ContentType" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_SignatureLine">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="issignatureline" type="s:ST_TrueFalse"/>
<xsd:attribute name="id" type="s:ST_Guid"/>
<xsd:attribute name="provid" type="s:ST_Guid"/>
<xsd:attribute name="signinginstructionsset" type="s:ST_TrueFalse"/>
<xsd:attribute name="allowcomments" type="s:ST_TrueFalse"/>
<xsd:attribute name="showsigndate" type="s:ST_TrueFalse"/>
<xsd:attribute name="suggestedsigner" type="xsd:string" form="qualified"/>
<xsd:attribute name="suggestedsigner2" type="xsd:string" form="qualified"/>
<xsd:attribute name="suggestedsigneremail" type="xsd:string" form="qualified"/>
<xsd:attribute name="signinginstructions" type="xsd:string"/>
<xsd:attribute name="addlxml" type="xsd:string"/>
<xsd:attribute name="sigprovurl" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="CT_ShapeLayout">
<xsd:all>
<xsd:element name="idmap" type="CT_IdMap" minOccurs="0"/>
<xsd:element name="regrouptable" type="CT_RegroupTable" minOccurs="0"/>
<xsd:element name="rules" type="CT_Rules" minOccurs="0"/>
</xsd:all>
<xsd:attributeGroup ref="v:AG_Ext"/>
</xsd:complexType>
<xsd:complexType name="CT_IdMap">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="data" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_RegroupTable">
<xsd:sequence>
<xsd:element name="entry" type="CT_Entry" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attributeGroup ref="v:AG_Ext"/>
</xsd:complexType>
<xsd:complexType name="CT_Entry">
<xsd:attribute name="new" type="xsd:int" use="optional"/>
<xsd:attribute name="old" type="xsd:int" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Rules">
<xsd:sequence>
<xsd:element name="r" type="CT_R" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attributeGroup ref="v:AG_Ext"/>
</xsd:complexType>
<xsd:complexType name="CT_R">
<xsd:sequence>
<xsd:element name="proxy" type="CT_Proxy" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string" use="required"/>
<xsd:attribute name="type" type="ST_RType" use="optional"/>
<xsd:attribute name="how" type="ST_How" use="optional"/>
<xsd:attribute name="idref" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Proxy">
<xsd:attribute name="start" type="s:ST_TrueFalseBlank" use="optional" default="false"/>
<xsd:attribute name="end" type="s:ST_TrueFalseBlank" use="optional" default="false"/>
<xsd:attribute name="idref" type="xsd:string" use="optional"/>
<xsd:attribute name="connectloc" type="xsd:int" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Diagram">
<xsd:sequence>
<xsd:element name="relationtable" type="CT_RelationTable" minOccurs="0"/>
</xsd:sequence>
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="dgmstyle" type="xsd:integer" use="optional"/>
<xsd:attribute name="autoformat" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="reverse" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="autolayout" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="dgmscalex" type="xsd:integer" use="optional"/>
<xsd:attribute name="dgmscaley" type="xsd:integer" use="optional"/>
<xsd:attribute name="dgmfontsize" type="xsd:integer" use="optional"/>
<xsd:attribute name="constrainbounds" type="xsd:string" use="optional"/>
<xsd:attribute name="dgmbasetextscale" type="xsd:integer" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_EquationXml">
<xsd:sequence>
<xsd:any namespace="##any"/>
</xsd:sequence>
<xsd:attribute name="contentType" type="ST_AlternateMathContentType" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="ST_AlternateMathContentType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:complexType name="CT_RelationTable">
<xsd:sequence>
<xsd:element name="rel" type="CT_Relation" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attributeGroup ref="v:AG_Ext"/>
</xsd:complexType>
<xsd:complexType name="CT_Relation">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="idsrc" type="xsd:string" use="optional"/>
<xsd:attribute name="iddest" type="xsd:string" use="optional"/>
<xsd:attribute name="idcntr" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_ColorMru">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="colors" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="CT_ColorMenu">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="strokecolor" type="s:ST_ColorType"/>
<xsd:attribute name="fillcolor" type="s:ST_ColorType"/>
<xsd:attribute name="shadowcolor" type="s:ST_ColorType"/>
<xsd:attribute name="extrusioncolor" type="s:ST_ColorType"/>
</xsd:complexType>
<xsd:element name="skew" type="CT_Skew"/>
<xsd:element name="extrusion" type="CT_Extrusion"/>
<xsd:element name="callout" type="CT_Callout"/>
<xsd:element name="lock" type="CT_Lock"/>
<xsd:element name="OLEObject" type="CT_OLEObject"/>
<xsd:element name="complex" type="CT_Complex"/>
<xsd:element name="left" type="CT_StrokeChild"/>
<xsd:element name="top" type="CT_StrokeChild"/>
<xsd:element name="right" type="CT_StrokeChild"/>
<xsd:element name="bottom" type="CT_StrokeChild"/>
<xsd:element name="column" type="CT_StrokeChild"/>
<xsd:element name="clippath" type="CT_ClipPath"/>
<xsd:element name="fill" type="CT_Fill"/>
<xsd:complexType name="CT_Skew">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="id" type="xsd:string" use="optional"/>
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="offset" type="xsd:string" use="optional"/>
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
<xsd:attribute name="matrix" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Extrusion">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="type" type="ST_ExtrusionType" default="parallel" use="optional"/>
<xsd:attribute name="render" type="ST_ExtrusionRender" default="solid" use="optional"/>
<xsd:attribute name="viewpointorigin" type="xsd:string" use="optional"/>
<xsd:attribute name="viewpoint" type="xsd:string" use="optional"/>
<xsd:attribute name="plane" type="ST_ExtrusionPlane" default="XY" use="optional"/>
<xsd:attribute name="skewangle" type="xsd:float" use="optional"/>
<xsd:attribute name="skewamt" type="xsd:string" use="optional"/>
<xsd:attribute name="foredepth" type="xsd:string" use="optional"/>
<xsd:attribute name="backdepth" type="xsd:string" use="optional"/>
<xsd:attribute name="orientation" type="xsd:string" use="optional"/>
<xsd:attribute name="orientationangle" type="xsd:float" use="optional"/>
<xsd:attribute name="lockrotationcenter" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="autorotationcenter" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="rotationcenter" type="xsd:string" use="optional"/>
<xsd:attribute name="rotationangle" type="xsd:string" use="optional"/>
<xsd:attribute name="colormode" type="ST_ColorMode" use="optional"/>
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="shininess" type="xsd:float" use="optional"/>
<xsd:attribute name="specularity" type="xsd:string" use="optional"/>
<xsd:attribute name="diffusity" type="xsd:string" use="optional"/>
<xsd:attribute name="metal" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="edge" type="xsd:string" use="optional"/>
<xsd:attribute name="facet" type="xsd:string" use="optional"/>
<xsd:attribute name="lightface" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="brightness" type="xsd:string" use="optional"/>
<xsd:attribute name="lightposition" type="xsd:string" use="optional"/>
<xsd:attribute name="lightlevel" type="xsd:string" use="optional"/>
<xsd:attribute name="lightharsh" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="lightposition2" type="xsd:string" use="optional"/>
<xsd:attribute name="lightlevel2" type="xsd:string" use="optional"/>
<xsd:attribute name="lightharsh2" type="s:ST_TrueFalse" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Callout">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="type" type="xsd:string" use="optional"/>
<xsd:attribute name="gap" type="xsd:string" use="optional"/>
<xsd:attribute name="angle" type="ST_Angle" use="optional"/>
<xsd:attribute name="dropauto" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="drop" type="ST_CalloutDrop" use="optional"/>
<xsd:attribute name="distance" type="xsd:string" use="optional"/>
<xsd:attribute name="lengthspecified" type="s:ST_TrueFalse" default="f" use="optional"/>
<xsd:attribute name="length" type="xsd:string" use="optional"/>
<xsd:attribute name="accentbar" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="textborder" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="minusx" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="minusy" type="s:ST_TrueFalse" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Lock">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="position" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="selection" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="grouping" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="ungrouping" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="rotation" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="cropping" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="verticies" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="adjusthandles" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="text" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="aspectratio" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="shapetype" type="s:ST_TrueFalse" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_OLEObject">
<xsd:sequence>
<xsd:element name="LinkType" type="ST_OLELinkType" minOccurs="0"/>
<xsd:element name="LockedField" type="s:ST_TrueFalseBlank" minOccurs="0"/>
<xsd:element name="FieldCodes" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="Type" type="ST_OLEType" use="optional"/>
<xsd:attribute name="ProgID" type="xsd:string" use="optional"/>
<xsd:attribute name="ShapeID" type="xsd:string" use="optional"/>
<xsd:attribute name="DrawAspect" type="ST_OLEDrawAspect" use="optional"/>
<xsd:attribute name="ObjectID" type="xsd:string" use="optional"/>
<xsd:attribute ref="r:id" use="optional"/>
<xsd:attribute name="UpdateMode" type="ST_OLEUpdateMode" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Complex">
<xsd:attributeGroup ref="v:AG_Ext"/>
</xsd:complexType>
<xsd:complexType name="CT_StrokeChild">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="weight" type="xsd:string" use="optional"/>
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
<xsd:attribute name="linestyle" type="v:ST_StrokeLineStyle" use="optional"/>
<xsd:attribute name="miterlimit" type="xsd:decimal" use="optional"/>
<xsd:attribute name="joinstyle" type="v:ST_StrokeJoinStyle" use="optional"/>
<xsd:attribute name="endcap" type="v:ST_StrokeEndCap" use="optional"/>
<xsd:attribute name="dashstyle" type="xsd:string" use="optional"/>
<xsd:attribute name="insetpen" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="filltype" type="v:ST_FillType" use="optional"/>
<xsd:attribute name="src" type="xsd:string" use="optional"/>
<xsd:attribute name="imageaspect" type="v:ST_ImageAspect" use="optional"/>
<xsd:attribute name="imagesize" type="xsd:string" use="optional"/>
<xsd:attribute name="imagealignshape" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="startarrow" type="v:ST_StrokeArrowType" use="optional"/>
<xsd:attribute name="startarrowwidth" type="v:ST_StrokeArrowWidth" use="optional"/>
<xsd:attribute name="startarrowlength" type="v:ST_StrokeArrowLength" use="optional"/>
<xsd:attribute name="endarrow" type="v:ST_StrokeArrowType" use="optional"/>
<xsd:attribute name="endarrowwidth" type="v:ST_StrokeArrowWidth" use="optional"/>
<xsd:attribute name="endarrowlength" type="v:ST_StrokeArrowLength" use="optional"/>
<xsd:attribute ref="href"/>
<xsd:attribute ref="althref"/>
<xsd:attribute ref="title"/>
<xsd:attribute ref="forcedash"/>
</xsd:complexType>
<xsd:complexType name="CT_ClipPath">
<xsd:attribute name="v" type="xsd:string" use="required" form="qualified"/>
</xsd:complexType>
<xsd:complexType name="CT_Fill">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="type" type="ST_FillType"/>
</xsd:complexType>
<xsd:simpleType name="ST_RType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="arc"/>
<xsd:enumeration value="callout"/>
<xsd:enumeration value="connector"/>
<xsd:enumeration value="align"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_How">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="top"/>
<xsd:enumeration value="middle"/>
<xsd:enumeration value="bottom"/>
<xsd:enumeration value="left"/>
<xsd:enumeration value="center"/>
<xsd:enumeration value="right"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_BWMode">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="color"/>
<xsd:enumeration value="auto"/>
<xsd:enumeration value="grayScale"/>
<xsd:enumeration value="lightGrayscale"/>
<xsd:enumeration value="inverseGray"/>
<xsd:enumeration value="grayOutline"/>
<xsd:enumeration value="highContrast"/>
<xsd:enumeration value="black"/>
<xsd:enumeration value="white"/>
<xsd:enumeration value="hide"/>
<xsd:enumeration value="undrawn"/>
<xsd:enumeration value="blackTextAndLines"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ScreenSize">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="544,376"/>
<xsd:enumeration value="640,480"/>
<xsd:enumeration value="720,512"/>
<xsd:enumeration value="800,600"/>
<xsd:enumeration value="1024,768"/>
<xsd:enumeration value="1152,862"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_InsetMode">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="auto"/>
<xsd:enumeration value="custom"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ColorMode">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="auto"/>
<xsd:enumeration value="custom"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ContentType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_DiagramLayout">
<xsd:restriction base="xsd:integer">
<xsd:enumeration value="0"/>
<xsd:enumeration value="1"/>
<xsd:enumeration value="2"/>
<xsd:enumeration value="3"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ExtrusionType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="perspective"/>
<xsd:enumeration value="parallel"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ExtrusionRender">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="solid"/>
<xsd:enumeration value="wireFrame"/>
<xsd:enumeration value="boundingCube"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ExtrusionPlane">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="XY"/>
<xsd:enumeration value="ZX"/>
<xsd:enumeration value="YZ"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_Angle">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="any"/>
<xsd:enumeration value="30"/>
<xsd:enumeration value="45"/>
<xsd:enumeration value="60"/>
<xsd:enumeration value="90"/>
<xsd:enumeration value="auto"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_CalloutDrop">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_CalloutPlacement">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="top"/>
<xsd:enumeration value="center"/>
<xsd:enumeration value="bottom"/>
<xsd:enumeration value="user"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ConnectorType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none"/>
<xsd:enumeration value="straight"/>
<xsd:enumeration value="elbow"/>
<xsd:enumeration value="curved"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_HrAlign">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="left"/>
<xsd:enumeration value="right"/>
<xsd:enumeration value="center"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ConnectType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none"/>
<xsd:enumeration value="rect"/>
<xsd:enumeration value="segments"/>
<xsd:enumeration value="custom"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_OLELinkType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_OLEType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Embed"/>
<xsd:enumeration value="Link"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_OLEDrawAspect">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Content"/>
<xsd:enumeration value="Icon"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_OLEUpdateMode">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Always"/>
<xsd:enumeration value="OnCall"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_FillType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="gradientCenter"/>
<xsd:enumeration value="solid"/>
<xsd:enumeration value="pattern"/>
<xsd:enumeration value="tile"/>
<xsd:enumeration value="frame"/>
<xsd:enumeration value="gradientUnscaled"/>
<xsd:enumeration value="gradientRadial"/>
<xsd:enumeration value="gradient"/>
<xsd:enumeration value="background"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="urn:schemas-microsoft-com:office:powerpoint"
targetNamespace="urn:schemas-microsoft-com:office:powerpoint" elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:element name="iscomment" type="CT_Empty"/>
<xsd:element name="textdata" type="CT_Rel"/>
<xsd:complexType name="CT_Empty"/>
<xsd:complexType name="CT_Rel">
<xsd:attribute name="id" type="xsd:string"/>
</xsd:complexType>
</xsd:schema>
@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="urn:schemas-microsoft-com:office:excel"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="urn:schemas-microsoft-com:office:excel" elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:element name="ClientData" type="CT_ClientData"/>
<xsd:complexType name="CT_ClientData">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="MoveWithCells" type="s:ST_TrueFalseBlank"/>
<xsd:element name="SizeWithCells" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Anchor" type="xsd:string"/>
<xsd:element name="Locked" type="s:ST_TrueFalseBlank"/>
<xsd:element name="DefaultSize" type="s:ST_TrueFalseBlank"/>
<xsd:element name="PrintObject" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Disabled" type="s:ST_TrueFalseBlank"/>
<xsd:element name="AutoFill" type="s:ST_TrueFalseBlank"/>
<xsd:element name="AutoLine" type="s:ST_TrueFalseBlank"/>
<xsd:element name="AutoPict" type="s:ST_TrueFalseBlank"/>
<xsd:element name="FmlaMacro" type="xsd:string"/>
<xsd:element name="TextHAlign" type="xsd:string"/>
<xsd:element name="TextVAlign" type="xsd:string"/>
<xsd:element name="LockText" type="s:ST_TrueFalseBlank"/>
<xsd:element name="JustLastX" type="s:ST_TrueFalseBlank"/>
<xsd:element name="SecretEdit" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Default" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Help" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Cancel" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Dismiss" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Accel" type="xsd:integer"/>
<xsd:element name="Accel2" type="xsd:integer"/>
<xsd:element name="Row" type="xsd:integer"/>
<xsd:element name="Column" type="xsd:integer"/>
<xsd:element name="Visible" type="s:ST_TrueFalseBlank"/>
<xsd:element name="RowHidden" type="s:ST_TrueFalseBlank"/>
<xsd:element name="ColHidden" type="s:ST_TrueFalseBlank"/>
<xsd:element name="VTEdit" type="xsd:integer"/>
<xsd:element name="MultiLine" type="s:ST_TrueFalseBlank"/>
<xsd:element name="VScroll" type="s:ST_TrueFalseBlank"/>
<xsd:element name="ValidIds" type="s:ST_TrueFalseBlank"/>
<xsd:element name="FmlaRange" type="xsd:string"/>
<xsd:element name="WidthMin" type="xsd:integer"/>
<xsd:element name="Sel" type="xsd:integer"/>
<xsd:element name="NoThreeD2" type="s:ST_TrueFalseBlank"/>
<xsd:element name="SelType" type="xsd:string"/>
<xsd:element name="MultiSel" type="xsd:string"/>
<xsd:element name="LCT" type="xsd:string"/>
<xsd:element name="ListItem" type="xsd:string"/>
<xsd:element name="DropStyle" type="xsd:string"/>
<xsd:element name="Colored" type="s:ST_TrueFalseBlank"/>
<xsd:element name="DropLines" type="xsd:integer"/>
<xsd:element name="Checked" type="xsd:integer"/>
<xsd:element name="FmlaLink" type="xsd:string"/>
<xsd:element name="FmlaPict" type="xsd:string"/>
<xsd:element name="NoThreeD" type="s:ST_TrueFalseBlank"/>
<xsd:element name="FirstButton" type="s:ST_TrueFalseBlank"/>
<xsd:element name="FmlaGroup" type="xsd:string"/>
<xsd:element name="Val" type="xsd:integer"/>
<xsd:element name="Min" type="xsd:integer"/>
<xsd:element name="Max" type="xsd:integer"/>
<xsd:element name="Inc" type="xsd:integer"/>
<xsd:element name="Page" type="xsd:integer"/>
<xsd:element name="Horiz" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Dx" type="xsd:integer"/>
<xsd:element name="MapOCX" type="s:ST_TrueFalseBlank"/>
<xsd:element name="CF" type="ST_CF"/>
<xsd:element name="Camera" type="s:ST_TrueFalseBlank"/>
<xsd:element name="RecalcAlways" type="s:ST_TrueFalseBlank"/>
<xsd:element name="AutoScale" type="s:ST_TrueFalseBlank"/>
<xsd:element name="DDE" type="s:ST_TrueFalseBlank"/>
<xsd:element name="UIObj" type="s:ST_TrueFalseBlank"/>
<xsd:element name="ScriptText" type="xsd:string"/>
<xsd:element name="ScriptExtended" type="xsd:string"/>
<xsd:element name="ScriptLanguage" type="xsd:nonNegativeInteger"/>
<xsd:element name="ScriptLocation" type="xsd:nonNegativeInteger"/>
<xsd:element name="FmlaTxbx" type="xsd:string"/>
</xsd:choice>
<xsd:attribute name="ObjectType" type="ST_ObjectType" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_CF">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_ObjectType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Button"/>
<xsd:enumeration value="Checkbox"/>
<xsd:enumeration value="Dialog"/>
<xsd:enumeration value="Drop"/>
<xsd:enumeration value="Edit"/>
<xsd:enumeration value="GBox"/>
<xsd:enumeration value="Label"/>
<xsd:enumeration value="LineA"/>
<xsd:enumeration value="List"/>
<xsd:enumeration value="Movie"/>
<xsd:enumeration value="Note"/>
<xsd:enumeration value="Pict"/>
<xsd:enumeration value="Radio"/>
<xsd:enumeration value="RectA"/>
<xsd:enumeration value="Scroll"/>
<xsd:enumeration value="Spin"/>
<xsd:enumeration value="Shape"/>
<xsd:enumeration value="Group"/>
<xsd:enumeration value="Rect"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="urn:schemas-microsoft-com:office:word"
targetNamespace="urn:schemas-microsoft-com:office:word" elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:element name="bordertop" type="CT_Border"/>
<xsd:element name="borderleft" type="CT_Border"/>
<xsd:element name="borderright" type="CT_Border"/>
<xsd:element name="borderbottom" type="CT_Border"/>
<xsd:complexType name="CT_Border">
<xsd:attribute name="type" type="ST_BorderType" use="optional"/>
<xsd:attribute name="width" type="xsd:positiveInteger" use="optional"/>
<xsd:attribute name="shadow" type="ST_BorderShadow" use="optional"/>
</xsd:complexType>
<xsd:element name="wrap" type="CT_Wrap"/>
<xsd:complexType name="CT_Wrap">
<xsd:attribute name="type" type="ST_WrapType" use="optional"/>
<xsd:attribute name="side" type="ST_WrapSide" use="optional"/>
<xsd:attribute name="anchorx" type="ST_HorizontalAnchor" use="optional"/>
<xsd:attribute name="anchory" type="ST_VerticalAnchor" use="optional"/>
</xsd:complexType>
<xsd:element name="anchorlock" type="CT_AnchorLock"/>
<xsd:complexType name="CT_AnchorLock"/>
<xsd:simpleType name="ST_BorderType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none"/>
<xsd:enumeration value="single"/>
<xsd:enumeration value="thick"/>
<xsd:enumeration value="double"/>
<xsd:enumeration value="hairline"/>
<xsd:enumeration value="dot"/>
<xsd:enumeration value="dash"/>
<xsd:enumeration value="dotDash"/>
<xsd:enumeration value="dashDotDot"/>
<xsd:enumeration value="triple"/>
<xsd:enumeration value="thinThickSmall"/>
<xsd:enumeration value="thickThinSmall"/>
<xsd:enumeration value="thickBetweenThinSmall"/>
<xsd:enumeration value="thinThick"/>
<xsd:enumeration value="thickThin"/>
<xsd:enumeration value="thickBetweenThin"/>
<xsd:enumeration value="thinThickLarge"/>
<xsd:enumeration value="thickThinLarge"/>
<xsd:enumeration value="thickBetweenThinLarge"/>
<xsd:enumeration value="wave"/>
<xsd:enumeration value="doubleWave"/>
<xsd:enumeration value="dashedSmall"/>
<xsd:enumeration value="dashDotStroked"/>
<xsd:enumeration value="threeDEmboss"/>
<xsd:enumeration value="threeDEngrave"/>
<xsd:enumeration value="HTMLOutset"/>
<xsd:enumeration value="HTMLInset"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_BorderShadow">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="t"/>
<xsd:enumeration value="true"/>
<xsd:enumeration value="f"/>
<xsd:enumeration value="false"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_WrapType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="topAndBottom"/>
<xsd:enumeration value="square"/>
<xsd:enumeration value="none"/>
<xsd:enumeration value="tight"/>
<xsd:enumeration value="through"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_WrapSide">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="both"/>
<xsd:enumeration value="left"/>
<xsd:enumeration value="right"/>
<xsd:enumeration value="largest"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_HorizontalAnchor">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="margin"/>
<xsd:enumeration value="page"/>
<xsd:enumeration value="text"/>
<xsd:enumeration value="char"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_VerticalAnchor">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="margin"/>
<xsd:enumeration value="page"/>
<xsd:enumeration value="text"/>
<xsd:enumeration value="line"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
@@ -0,0 +1,116 @@
<?xml version='1.0'?>
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">
<xs:annotation>
<xs:documentation>
See http://www.w3.org/XML/1998/namespace.html and
http://www.w3.org/TR/REC-xml for information about this namespace.
This schema document describes the XML namespace, in a form
suitable for import by other schema documents.
Note that local names in this namespace are intended to be defined
only by the World Wide Web Consortium or its subgroups. The
following names are currently defined in this namespace and should
not be used with conflicting semantics by any Working Group,
specification, or document instance:
base (as an attribute name): denotes an attribute whose value
provides a URI to be used as the base for interpreting any
relative URIs in the scope of the element on which it
appears; its value is inherited. This name is reserved
by virtue of its definition in the XML Base specification.
lang (as an attribute name): denotes an attribute whose value
is a language code for the natural language of the content of
any element; its value is inherited. This name is reserved
by virtue of its definition in the XML specification.
space (as an attribute name): denotes an attribute whose
value is a keyword indicating what whitespace processing
discipline is intended for the content of the element; its
value is inherited. This name is reserved by virtue of its
definition in the XML specification.
Father (in any context at all): denotes Jon Bosak, the chair of
the original XML Working Group. This name is reserved by
the following decision of the W3C XML Plenary and
XML Coordination groups:
In appreciation for his vision, leadership and dedication
the W3C XML Plenary on this 10th day of February, 2000
reserves for Jon Bosak in perpetuity the XML name
xml:Father
</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>This schema defines attributes and an attribute group
suitable for use by
schemas wishing to allow xml:base, xml:lang or xml:space attributes
on elements they define.
To enable this, such a schema must import this schema
for the XML namespace, e.g. as follows:
&lt;schema . . .>
. . .
&lt;import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2001/03/xml.xsd"/>
Subsequently, qualified reference to any of the attributes
or the group defined below will have the desired effect, e.g.
&lt;type . . .>
. . .
&lt;attributeGroup ref="xml:specialAttrs"/>
will define a type which will schema-validate an instance
element with any of those attributes</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>In keeping with the XML Schema WG's standard versioning
policy, this schema document will persist at
http://www.w3.org/2001/03/xml.xsd.
At the date of issue it can also be found at
http://www.w3.org/2001/xml.xsd.
The schema document at that URI may however change in the future,
in order to remain compatible with the latest version of XML Schema
itself. In other words, if the XML Schema namespace changes, the version
of this document at
http://www.w3.org/2001/xml.xsd will change
accordingly; the version at
http://www.w3.org/2001/03/xml.xsd will not change.
</xs:documentation>
</xs:annotation>
<xs:attribute name="lang" type="xs:language">
<xs:annotation>
<xs:documentation>In due course, we should install the relevant ISO 2- and 3-letter
codes as the enumerated possible values . . .</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="space" default="preserve">
<xs:simpleType>
<xs:restriction base="xs:NCName">
<xs:enumeration value="default"/>
<xs:enumeration value="preserve"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="base" type="xs:anyURI">
<xs:annotation>
<xs:documentation>See http://www.w3.org/TR/xmlbase/ for
information about this attribute.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attributeGroup name="specialAttrs">
<xs:attribute ref="xml:base"/>
<xs:attribute ref="xml:lang"/>
<xs:attribute ref="xml:space"/>
</xs:attributeGroup>
</xs:schema>
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xs:schema xmlns="http://schemas.openxmlformats.org/package/2006/content-types"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://schemas.openxmlformats.org/package/2006/content-types"
elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all">
<xs:element name="Types" type="CT_Types"/>
<xs:element name="Default" type="CT_Default"/>
<xs:element name="Override" type="CT_Override"/>
<xs:complexType name="CT_Types">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="Default"/>
<xs:element ref="Override"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="CT_Default">
<xs:attribute name="Extension" type="ST_Extension" use="required"/>
<xs:attribute name="ContentType" type="ST_ContentType" use="required"/>
</xs:complexType>
<xs:complexType name="CT_Override">
<xs:attribute name="ContentType" type="ST_ContentType" use="required"/>
<xs:attribute name="PartName" type="xs:anyURI" use="required"/>
</xs:complexType>
<xs:simpleType name="ST_ContentType">
<xs:restriction base="xs:string">
<xs:pattern
value="(((([\p{IsBasicLatin}-[\p{Cc}&#127;\(\)&lt;&gt;@,;:\\&quot;/\[\]\?=\{\}\s\t]])+))/((([\p{IsBasicLatin}-[\p{Cc}&#127;\(\)&lt;&gt;@,;:\\&quot;/\[\]\?=\{\}\s\t]])+))((\s+)*;(\s+)*(((([\p{IsBasicLatin}-[\p{Cc}&#127;\(\)&lt;&gt;@,;:\\&quot;/\[\]\?=\{\}\s\t]])+))=((([\p{IsBasicLatin}-[\p{Cc}&#127;\(\)&lt;&gt;@,;:\\&quot;/\[\]\?=\{\}\s\t]])+)|(&quot;(([\p{IsLatin-1Supplement}\p{IsBasicLatin}-[\p{Cc}&#127;&quot;\n\r]]|(\s+))|(\\[\p{IsBasicLatin}]))*&quot;))))*)"
/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="ST_Extension">
<xs:restriction base="xs:string">
<xs:pattern
value="([!$&amp;'\(\)\*\+,:=]|(%[0-9a-fA-F][0-9a-fA-F])|[:@]|[a-zA-Z0-9\-_~])+"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
xmlns="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:dcterms="http://purl.org/dc/terms/" elementFormDefault="qualified" blockDefault="#all">
<xs:import namespace="http://purl.org/dc/elements/1.1/"
schemaLocation="http://dublincore.org/schemas/xmls/qdc/2003/04/02/dc.xsd"/>
<xs:import namespace="http://purl.org/dc/terms/"
schemaLocation="http://dublincore.org/schemas/xmls/qdc/2003/04/02/dcterms.xsd"/>
<xs:import id="xml" namespace="http://www.w3.org/XML/1998/namespace"/>
<xs:element name="coreProperties" type="CT_CoreProperties"/>
<xs:complexType name="CT_CoreProperties">
<xs:all>
<xs:element name="category" minOccurs="0" maxOccurs="1" type="xs:string"/>
<xs:element name="contentStatus" minOccurs="0" maxOccurs="1" type="xs:string"/>
<xs:element ref="dcterms:created" minOccurs="0" maxOccurs="1"/>
<xs:element ref="dc:creator" minOccurs="0" maxOccurs="1"/>
<xs:element ref="dc:description" minOccurs="0" maxOccurs="1"/>
<xs:element ref="dc:identifier" minOccurs="0" maxOccurs="1"/>
<xs:element name="keywords" minOccurs="0" maxOccurs="1" type="CT_Keywords"/>
<xs:element ref="dc:language" minOccurs="0" maxOccurs="1"/>
<xs:element name="lastModifiedBy" minOccurs="0" maxOccurs="1" type="xs:string"/>
<xs:element name="lastPrinted" minOccurs="0" maxOccurs="1" type="xs:dateTime"/>
<xs:element ref="dcterms:modified" minOccurs="0" maxOccurs="1"/>
<xs:element name="revision" minOccurs="0" maxOccurs="1" type="xs:string"/>
<xs:element ref="dc:subject" minOccurs="0" maxOccurs="1"/>
<xs:element ref="dc:title" minOccurs="0" maxOccurs="1"/>
<xs:element name="version" minOccurs="0" maxOccurs="1" type="xs:string"/>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_Keywords" mixed="true">
<xs:sequence>
<xs:element name="value" minOccurs="0" maxOccurs="unbounded" type="CT_Keyword"/>
</xs:sequence>
<xs:attribute ref="xml:lang" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_Keyword">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute ref="xml:lang" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://schemas.openxmlformats.org/package/2006/digital-signature"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://schemas.openxmlformats.org/package/2006/digital-signature"
elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all">
<xsd:element name="SignatureTime" type="CT_SignatureTime"/>
<xsd:element name="RelationshipReference" type="CT_RelationshipReference"/>
<xsd:element name="RelationshipsGroupReference" type="CT_RelationshipsGroupReference"/>
<xsd:complexType name="CT_SignatureTime">
<xsd:sequence>
<xsd:element name="Format" type="ST_Format"/>
<xsd:element name="Value" type="ST_Value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_RelationshipReference">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="SourceId" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:complexType name="CT_RelationshipsGroupReference">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="SourceType" type="xsd:anyURI" use="required"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:simpleType name="ST_Format">
<xsd:restriction base="xsd:string">
<xsd:pattern
value="(YYYY)|(YYYY-MM)|(YYYY-MM-DD)|(YYYY-MM-DDThh:mmTZD)|(YYYY-MM-DDThh:mm:ssTZD)|(YYYY-MM-DDThh:mm:ss.sTZD)"
/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_Value">
<xsd:restriction base="xsd:string">
<xsd:pattern
value="(([0-9][0-9][0-9][0-9]))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2))))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1))))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1)))T((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))(((\+|-)((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])))|Z))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1)))T((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))(((\+|-)((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])))|Z))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1)))T((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])):(((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))\.[0-9])(((\+|-)((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])))|Z))"
/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://schemas.openxmlformats.org/package/2006/relationships"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://schemas.openxmlformats.org/package/2006/relationships"
elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all">
<xsd:element name="Relationships" type="CT_Relationships"/>
<xsd:element name="Relationship" type="CT_Relationship"/>
<xsd:complexType name="CT_Relationships">
<xsd:sequence>
<xsd:element ref="Relationship" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Relationship">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="TargetMode" type="ST_TargetMode" use="optional"/>
<xsd:attribute name="Target" type="xsd:anyURI" use="required"/>
<xsd:attribute name="Type" type="xsd:anyURI" use="required"/>
<xsd:attribute name="Id" type="xsd:ID" use="required"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:simpleType name="ST_TargetMode">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="External"/>
<xsd:enumeration value="Internal"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
attributeFormDefault="unqualified" elementFormDefault="qualified"
targetNamespace="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!--
This XSD is a modified version of the one found at:
https://github.com/plutext/docx4j/blob/master/xsd/mce/markup-compatibility-2006-MINIMAL.xsd
This XSD has 2 objectives:
1. round tripping @mc:Ignorable
<w:document
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
mc:Ignorable="w14 w15 wp14">
2. enabling AlternateContent to be manipulated in certain elements
(in the unusual case where the content model is xsd:any, it doesn't have to be explicitly added)
See further ECMA-376, 4th Edition, Office Open XML File Formats
Part 3 : Markup Compatibility and Extensibility
-->
<!-- Objective 1 -->
<xsd:attribute name="Ignorable" type="xsd:string" />
<!-- Objective 2 -->
<xsd:attribute name="MustUnderstand" type="xsd:string" />
<xsd:attribute name="ProcessContent" type="xsd:string" />
<!-- An AlternateContent element shall contain one or more Choice child elements, optionally followed by a
Fallback child element. If present, there shall be only one Fallback element, and it shall follow all Choice
elements. -->
<xsd:element name="AlternateContent">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Choice" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:any minOccurs="0" maxOccurs="unbounded"
processContents="strict">
</xsd:any>
</xsd:sequence>
<xsd:attribute name="Requires" type="xsd:string" use="required" />
<xsd:attribute ref="mc:Ignorable" use="optional" />
<xsd:attribute ref="mc:MustUnderstand" use="optional" />
<xsd:attribute ref="mc:ProcessContent" use="optional" />
</xsd:complexType>
</xsd:element>
<xsd:element name="Fallback" minOccurs="0" maxOccurs="1">
<xsd:complexType>
<xsd:sequence>
<xsd:any minOccurs="0" maxOccurs="unbounded"
processContents="strict">
</xsd:any>
</xsd:sequence>
<xsd:attribute ref="mc:Ignorable" use="optional" />
<xsd:attribute ref="mc:MustUnderstand" use="optional" />
<xsd:attribute ref="mc:ProcessContent" use="optional" />
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<!-- AlternateContent elements might include the attributes Ignorable,
MustUnderstand and ProcessContent described in this Part of ECMA-376. These
attributes’ qualified names shall be prefixed when associated with an AlternateContent
element. -->
<xsd:attribute ref="mc:Ignorable" use="optional" />
<xsd:attribute ref="mc:MustUnderstand" use="optional" />
<xsd:attribute ref="mc:ProcessContent" use="optional" />
</xsd:complexType>
</xsd:element>
</xsd:schema>
@@ -0,0 +1,560 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns="http://schemas.microsoft.com/office/word/2010/wordml" targetNamespace="http://schemas.microsoft.com/office/word/2010/wordml">
<!-- <xsd:import id="rel" namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships" schemaLocation="orel.xsd"/> -->
<xsd:import id="w" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
<!-- <xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main" schemaLocation="oartbasetypes.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main" schemaLocation="oartsplineproperties.xsd"/> -->
<xsd:complexType name="CT_LongHexNumber">
<xsd:attribute name="val" type="w:ST_LongHexNumber" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_OnOff">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="true"/>
<xsd:enumeration value="false"/>
<xsd:enumeration value="0"/>
<xsd:enumeration value="1"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_OnOff">
<xsd:attribute name="val" type="ST_OnOff"/>
</xsd:complexType>
<xsd:element name="docId" type="CT_LongHexNumber"/>
<xsd:element name="conflictMode" type="CT_OnOff"/>
<xsd:attributeGroup name="AG_Parids">
<xsd:attribute name="paraId" type="w:ST_LongHexNumber"/>
<xsd:attribute name="textId" type="w:ST_LongHexNumber"/>
</xsd:attributeGroup>
<xsd:attribute name="anchorId" type="w:ST_LongHexNumber"/>
<xsd:attribute name="noSpellErr" type="ST_OnOff"/>
<xsd:element name="customXmlConflictInsRangeStart" type="w:CT_TrackChange"/>
<xsd:element name="customXmlConflictInsRangeEnd" type="w:CT_Markup"/>
<xsd:element name="customXmlConflictDelRangeStart" type="w:CT_TrackChange"/>
<xsd:element name="customXmlConflictDelRangeEnd" type="w:CT_Markup"/>
<xsd:group name="EG_RunLevelConflicts">
<xsd:sequence>
<xsd:element name="conflictIns" type="w:CT_RunTrackChange" minOccurs="0"/>
<xsd:element name="conflictDel" type="w:CT_RunTrackChange" minOccurs="0"/>
</xsd:sequence>
</xsd:group>
<xsd:group name="EG_Conflicts">
<xsd:choice>
<xsd:element name="conflictIns" type="w:CT_TrackChange" minOccurs="0"/>
<xsd:element name="conflictDel" type="w:CT_TrackChange" minOccurs="0"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_Percentage">
<xsd:attribute name="val" type="a:ST_Percentage" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_PositiveFixedPercentage">
<xsd:attribute name="val" type="a:ST_PositiveFixedPercentage" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_PositivePercentage">
<xsd:attribute name="val" type="a:ST_PositivePercentage" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_SchemeColorVal">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="bg1"/>
<xsd:enumeration value="tx1"/>
<xsd:enumeration value="bg2"/>
<xsd:enumeration value="tx2"/>
<xsd:enumeration value="accent1"/>
<xsd:enumeration value="accent2"/>
<xsd:enumeration value="accent3"/>
<xsd:enumeration value="accent4"/>
<xsd:enumeration value="accent5"/>
<xsd:enumeration value="accent6"/>
<xsd:enumeration value="hlink"/>
<xsd:enumeration value="folHlink"/>
<xsd:enumeration value="dk1"/>
<xsd:enumeration value="lt1"/>
<xsd:enumeration value="dk2"/>
<xsd:enumeration value="lt2"/>
<xsd:enumeration value="phClr"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_RectAlignment">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none"/>
<xsd:enumeration value="tl"/>
<xsd:enumeration value="t"/>
<xsd:enumeration value="tr"/>
<xsd:enumeration value="l"/>
<xsd:enumeration value="ctr"/>
<xsd:enumeration value="r"/>
<xsd:enumeration value="bl"/>
<xsd:enumeration value="b"/>
<xsd:enumeration value="br"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_PathShadeType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="shape"/>
<xsd:enumeration value="circle"/>
<xsd:enumeration value="rect"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_LineCap">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="rnd"/>
<xsd:enumeration value="sq"/>
<xsd:enumeration value="flat"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_PresetLineDashVal">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="solid"/>
<xsd:enumeration value="dot"/>
<xsd:enumeration value="sysDot"/>
<xsd:enumeration value="dash"/>
<xsd:enumeration value="sysDash"/>
<xsd:enumeration value="lgDash"/>
<xsd:enumeration value="dashDot"/>
<xsd:enumeration value="sysDashDot"/>
<xsd:enumeration value="lgDashDot"/>
<xsd:enumeration value="lgDashDotDot"/>
<xsd:enumeration value="sysDashDotDot"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_PenAlignment">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="ctr"/>
<xsd:enumeration value="in"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_CompoundLine">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="sng"/>
<xsd:enumeration value="dbl"/>
<xsd:enumeration value="thickThin"/>
<xsd:enumeration value="thinThick"/>
<xsd:enumeration value="tri"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_RelativeRect">
<xsd:attribute name="l" use="optional" type="a:ST_Percentage"/>
<xsd:attribute name="t" use="optional" type="a:ST_Percentage"/>
<xsd:attribute name="r" use="optional" type="a:ST_Percentage"/>
<xsd:attribute name="b" use="optional" type="a:ST_Percentage"/>
</xsd:complexType>
<xsd:group name="EG_ColorTransform">
<xsd:choice>
<xsd:element name="tint" type="CT_PositiveFixedPercentage"/>
<xsd:element name="shade" type="CT_PositiveFixedPercentage"/>
<xsd:element name="alpha" type="CT_PositiveFixedPercentage"/>
<xsd:element name="hueMod" type="CT_PositivePercentage"/>
<xsd:element name="sat" type="CT_Percentage"/>
<xsd:element name="satOff" type="CT_Percentage"/>
<xsd:element name="satMod" type="CT_Percentage"/>
<xsd:element name="lum" type="CT_Percentage"/>
<xsd:element name="lumOff" type="CT_Percentage"/>
<xsd:element name="lumMod" type="CT_Percentage"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_SRgbColor">
<xsd:sequence>
<xsd:group ref="EG_ColorTransform" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="val" type="s:ST_HexColorRGB" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_SchemeColor">
<xsd:sequence>
<xsd:group ref="EG_ColorTransform" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="val" type="ST_SchemeColorVal" use="required"/>
</xsd:complexType>
<xsd:group name="EG_ColorChoice">
<xsd:choice>
<xsd:element name="srgbClr" type="CT_SRgbColor"/>
<xsd:element name="schemeClr" type="CT_SchemeColor"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_Color">
<xsd:sequence>
<xsd:group ref="EG_ColorChoice"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GradientStop">
<xsd:sequence>
<xsd:group ref="EG_ColorChoice"/>
</xsd:sequence>
<xsd:attribute name="pos" type="a:ST_PositiveFixedPercentage" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_GradientStopList">
<xsd:sequence>
<xsd:element name="gs" type="CT_GradientStop" minOccurs="2" maxOccurs="10"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_LinearShadeProperties">
<xsd:attribute name="ang" type="a:ST_PositiveFixedAngle" use="optional"/>
<xsd:attribute name="scaled" type="ST_OnOff" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_PathShadeProperties">
<xsd:sequence>
<xsd:element name="fillToRect" type="CT_RelativeRect" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="path" type="ST_PathShadeType" use="optional"/>
</xsd:complexType>
<xsd:group name="EG_ShadeProperties">
<xsd:choice>
<xsd:element name="lin" type="CT_LinearShadeProperties"/>
<xsd:element name="path" type="CT_PathShadeProperties"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_SolidColorFillProperties">
<xsd:sequence>
<xsd:group ref="EG_ColorChoice" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GradientFillProperties">
<xsd:sequence>
<xsd:element name="gsLst" type="CT_GradientStopList" minOccurs="0"/>
<xsd:group ref="EG_ShadeProperties" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:group name="EG_FillProperties">
<xsd:choice>
<xsd:element name="noFill" type="w:CT_Empty"/>
<xsd:element name="solidFill" type="CT_SolidColorFillProperties"/>
<xsd:element name="gradFill" type="CT_GradientFillProperties"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_PresetLineDashProperties">
<xsd:attribute name="val" type="ST_PresetLineDashVal" use="optional"/>
</xsd:complexType>
<xsd:group name="EG_LineDashProperties">
<xsd:choice>
<xsd:element name="prstDash" type="CT_PresetLineDashProperties"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_LineJoinMiterProperties">
<xsd:attribute name="lim" type="a:ST_PositivePercentage" use="optional"/>
</xsd:complexType>
<xsd:group name="EG_LineJoinProperties">
<xsd:choice>
<xsd:element name="round" type="w:CT_Empty"/>
<xsd:element name="bevel" type="w:CT_Empty"/>
<xsd:element name="miter" type="CT_LineJoinMiterProperties"/>
</xsd:choice>
</xsd:group>
<xsd:simpleType name="ST_PresetCameraType">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="legacyObliqueTopLeft"/>
<xsd:enumeration value="legacyObliqueTop"/>
<xsd:enumeration value="legacyObliqueTopRight"/>
<xsd:enumeration value="legacyObliqueLeft"/>
<xsd:enumeration value="legacyObliqueFront"/>
<xsd:enumeration value="legacyObliqueRight"/>
<xsd:enumeration value="legacyObliqueBottomLeft"/>
<xsd:enumeration value="legacyObliqueBottom"/>
<xsd:enumeration value="legacyObliqueBottomRight"/>
<xsd:enumeration value="legacyPerspectiveTopLeft"/>
<xsd:enumeration value="legacyPerspectiveTop"/>
<xsd:enumeration value="legacyPerspectiveTopRight"/>
<xsd:enumeration value="legacyPerspectiveLeft"/>
<xsd:enumeration value="legacyPerspectiveFront"/>
<xsd:enumeration value="legacyPerspectiveRight"/>
<xsd:enumeration value="legacyPerspectiveBottomLeft"/>
<xsd:enumeration value="legacyPerspectiveBottom"/>
<xsd:enumeration value="legacyPerspectiveBottomRight"/>
<xsd:enumeration value="orthographicFront"/>
<xsd:enumeration value="isometricTopUp"/>
<xsd:enumeration value="isometricTopDown"/>
<xsd:enumeration value="isometricBottomUp"/>
<xsd:enumeration value="isometricBottomDown"/>
<xsd:enumeration value="isometricLeftUp"/>
<xsd:enumeration value="isometricLeftDown"/>
<xsd:enumeration value="isometricRightUp"/>
<xsd:enumeration value="isometricRightDown"/>
<xsd:enumeration value="isometricOffAxis1Left"/>
<xsd:enumeration value="isometricOffAxis1Right"/>
<xsd:enumeration value="isometricOffAxis1Top"/>
<xsd:enumeration value="isometricOffAxis2Left"/>
<xsd:enumeration value="isometricOffAxis2Right"/>
<xsd:enumeration value="isometricOffAxis2Top"/>
<xsd:enumeration value="isometricOffAxis3Left"/>
<xsd:enumeration value="isometricOffAxis3Right"/>
<xsd:enumeration value="isometricOffAxis3Bottom"/>
<xsd:enumeration value="isometricOffAxis4Left"/>
<xsd:enumeration value="isometricOffAxis4Right"/>
<xsd:enumeration value="isometricOffAxis4Bottom"/>
<xsd:enumeration value="obliqueTopLeft"/>
<xsd:enumeration value="obliqueTop"/>
<xsd:enumeration value="obliqueTopRight"/>
<xsd:enumeration value="obliqueLeft"/>
<xsd:enumeration value="obliqueRight"/>
<xsd:enumeration value="obliqueBottomLeft"/>
<xsd:enumeration value="obliqueBottom"/>
<xsd:enumeration value="obliqueBottomRight"/>
<xsd:enumeration value="perspectiveFront"/>
<xsd:enumeration value="perspectiveLeft"/>
<xsd:enumeration value="perspectiveRight"/>
<xsd:enumeration value="perspectiveAbove"/>
<xsd:enumeration value="perspectiveBelow"/>
<xsd:enumeration value="perspectiveAboveLeftFacing"/>
<xsd:enumeration value="perspectiveAboveRightFacing"/>
<xsd:enumeration value="perspectiveContrastingLeftFacing"/>
<xsd:enumeration value="perspectiveContrastingRightFacing"/>
<xsd:enumeration value="perspectiveHeroicLeftFacing"/>
<xsd:enumeration value="perspectiveHeroicRightFacing"/>
<xsd:enumeration value="perspectiveHeroicExtremeLeftFacing"/>
<xsd:enumeration value="perspectiveHeroicExtremeRightFacing"/>
<xsd:enumeration value="perspectiveRelaxed"/>
<xsd:enumeration value="perspectiveRelaxedModerately"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Camera">
<xsd:attribute name="prst" use="required" type="ST_PresetCameraType"/>
</xsd:complexType>
<xsd:complexType name="CT_SphereCoords">
<xsd:attribute name="lat" type="a:ST_PositiveFixedAngle" use="required"/>
<xsd:attribute name="lon" type="a:ST_PositiveFixedAngle" use="required"/>
<xsd:attribute name="rev" type="a:ST_PositiveFixedAngle" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_LightRigType">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="legacyFlat1"/>
<xsd:enumeration value="legacyFlat2"/>
<xsd:enumeration value="legacyFlat3"/>
<xsd:enumeration value="legacyFlat4"/>
<xsd:enumeration value="legacyNormal1"/>
<xsd:enumeration value="legacyNormal2"/>
<xsd:enumeration value="legacyNormal3"/>
<xsd:enumeration value="legacyNormal4"/>
<xsd:enumeration value="legacyHarsh1"/>
<xsd:enumeration value="legacyHarsh2"/>
<xsd:enumeration value="legacyHarsh3"/>
<xsd:enumeration value="legacyHarsh4"/>
<xsd:enumeration value="threePt"/>
<xsd:enumeration value="balanced"/>
<xsd:enumeration value="soft"/>
<xsd:enumeration value="harsh"/>
<xsd:enumeration value="flood"/>
<xsd:enumeration value="contrasting"/>
<xsd:enumeration value="morning"/>
<xsd:enumeration value="sunrise"/>
<xsd:enumeration value="sunset"/>
<xsd:enumeration value="chilly"/>
<xsd:enumeration value="freezing"/>
<xsd:enumeration value="flat"/>
<xsd:enumeration value="twoPt"/>
<xsd:enumeration value="glow"/>
<xsd:enumeration value="brightRoom"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_LightRigDirection">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="tl"/>
<xsd:enumeration value="t"/>
<xsd:enumeration value="tr"/>
<xsd:enumeration value="l"/>
<xsd:enumeration value="r"/>
<xsd:enumeration value="bl"/>
<xsd:enumeration value="b"/>
<xsd:enumeration value="br"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_LightRig">
<xsd:sequence>
<xsd:element name="rot" type="CT_SphereCoords" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="rig" type="ST_LightRigType" use="required"/>
<xsd:attribute name="dir" type="ST_LightRigDirection" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_BevelPresetType">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="relaxedInset"/>
<xsd:enumeration value="circle"/>
<xsd:enumeration value="slope"/>
<xsd:enumeration value="cross"/>
<xsd:enumeration value="angle"/>
<xsd:enumeration value="softRound"/>
<xsd:enumeration value="convex"/>
<xsd:enumeration value="coolSlant"/>
<xsd:enumeration value="divot"/>
<xsd:enumeration value="riblet"/>
<xsd:enumeration value="hardEdge"/>
<xsd:enumeration value="artDeco"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Bevel">
<xsd:attribute name="w" type="a:ST_PositiveCoordinate" use="optional"/>
<xsd:attribute name="h" type="a:ST_PositiveCoordinate" use="optional"/>
<xsd:attribute name="prst" type="ST_BevelPresetType" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="ST_PresetMaterialType">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="legacyMatte"/>
<xsd:enumeration value="legacyPlastic"/>
<xsd:enumeration value="legacyMetal"/>
<xsd:enumeration value="legacyWireframe"/>
<xsd:enumeration value="matte"/>
<xsd:enumeration value="plastic"/>
<xsd:enumeration value="metal"/>
<xsd:enumeration value="warmMatte"/>
<xsd:enumeration value="translucentPowder"/>
<xsd:enumeration value="powder"/>
<xsd:enumeration value="dkEdge"/>
<xsd:enumeration value="softEdge"/>
<xsd:enumeration value="clear"/>
<xsd:enumeration value="flat"/>
<xsd:enumeration value="softmetal"/>
<xsd:enumeration value="none"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Glow">
<xsd:sequence>
<xsd:group ref="EG_ColorChoice"/>
</xsd:sequence>
<xsd:attribute name="rad" use="optional" type="a:ST_PositiveCoordinate"/>
</xsd:complexType>
<xsd:complexType name="CT_Shadow">
<xsd:sequence>
<xsd:group ref="EG_ColorChoice"/>
</xsd:sequence>
<xsd:attribute name="blurRad" use="optional" type="a:ST_PositiveCoordinate"/>
<xsd:attribute name="dist" use="optional" type="a:ST_PositiveCoordinate"/>
<xsd:attribute name="dir" use="optional" type="a:ST_PositiveFixedAngle"/>
<xsd:attribute name="sx" use="optional" type="a:ST_Percentage"/>
<xsd:attribute name="sy" use="optional" type="a:ST_Percentage"/>
<xsd:attribute name="kx" use="optional" type="a:ST_FixedAngle"/>
<xsd:attribute name="ky" use="optional" type="a:ST_FixedAngle"/>
<xsd:attribute name="algn" use="optional" type="ST_RectAlignment"/>
</xsd:complexType>
<xsd:complexType name="CT_Reflection">
<xsd:attribute name="blurRad" use="optional" type="a:ST_PositiveCoordinate"/>
<xsd:attribute name="stA" use="optional" type="a:ST_PositiveFixedPercentage"/>
<xsd:attribute name="stPos" use="optional" type="a:ST_PositiveFixedPercentage"/>
<xsd:attribute name="endA" use="optional" type="a:ST_PositiveFixedPercentage"/>
<xsd:attribute name="endPos" use="optional" type="a:ST_PositiveFixedPercentage"/>
<xsd:attribute name="dist" use="optional" type="a:ST_PositiveCoordinate"/>
<xsd:attribute name="dir" use="optional" type="a:ST_PositiveFixedAngle"/>
<xsd:attribute name="fadeDir" use="optional" type="a:ST_PositiveFixedAngle"/>
<xsd:attribute name="sx" use="optional" type="a:ST_Percentage"/>
<xsd:attribute name="sy" use="optional" type="a:ST_Percentage"/>
<xsd:attribute name="kx" use="optional" type="a:ST_FixedAngle"/>
<xsd:attribute name="ky" use="optional" type="a:ST_FixedAngle"/>
<xsd:attribute name="algn" use="optional" type="ST_RectAlignment"/>
</xsd:complexType>
<xsd:complexType name="CT_FillTextEffect">
<xsd:sequence>
<xsd:group ref="EG_FillProperties" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_TextOutlineEffect">
<xsd:sequence>
<xsd:group ref="EG_FillProperties" minOccurs="0"/>
<xsd:group ref="EG_LineDashProperties" minOccurs="0"/>
<xsd:group ref="EG_LineJoinProperties" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="w" use="optional" type="a:ST_LineWidth"/>
<xsd:attribute name="cap" use="optional" type="ST_LineCap"/>
<xsd:attribute name="cmpd" use="optional" type="ST_CompoundLine"/>
<xsd:attribute name="algn" use="optional" type="ST_PenAlignment"/>
</xsd:complexType>
<xsd:complexType name="CT_Scene3D">
<xsd:sequence>
<xsd:element name="camera" type="CT_Camera"/>
<xsd:element name="lightRig" type="CT_LightRig"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Props3D">
<xsd:sequence>
<xsd:element name="bevelT" type="CT_Bevel" minOccurs="0"/>
<xsd:element name="bevelB" type="CT_Bevel" minOccurs="0"/>
<xsd:element name="extrusionClr" type="CT_Color" minOccurs="0"/>
<xsd:element name="contourClr" type="CT_Color" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="extrusionH" type="a:ST_PositiveCoordinate" use="optional"/>
<xsd:attribute name="contourW" type="a:ST_PositiveCoordinate" use="optional"/>
<xsd:attribute name="prstMaterial" type="ST_PresetMaterialType" use="optional"/>
</xsd:complexType>
<xsd:group name="EG_RPrTextEffects">
<xsd:sequence>
<xsd:element name="glow" minOccurs="0" type="CT_Glow"/>
<xsd:element name="shadow" minOccurs="0" type="CT_Shadow"/>
<xsd:element name="reflection" minOccurs="0" type="CT_Reflection"/>
<xsd:element name="textOutline" minOccurs="0" type="CT_TextOutlineEffect"/>
<xsd:element name="textFill" minOccurs="0" type="CT_FillTextEffect"/>
<xsd:element name="scene3d" minOccurs="0" type="CT_Scene3D"/>
<xsd:element name="props3d" minOccurs="0" type="CT_Props3D"/>
</xsd:sequence>
</xsd:group>
<xsd:simpleType name="ST_Ligatures">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none"/>
<xsd:enumeration value="standard"/>
<xsd:enumeration value="contextual"/>
<xsd:enumeration value="historical"/>
<xsd:enumeration value="discretional"/>
<xsd:enumeration value="standardContextual"/>
<xsd:enumeration value="standardHistorical"/>
<xsd:enumeration value="contextualHistorical"/>
<xsd:enumeration value="standardDiscretional"/>
<xsd:enumeration value="contextualDiscretional"/>
<xsd:enumeration value="historicalDiscretional"/>
<xsd:enumeration value="standardContextualHistorical"/>
<xsd:enumeration value="standardContextualDiscretional"/>
<xsd:enumeration value="standardHistoricalDiscretional"/>
<xsd:enumeration value="contextualHistoricalDiscretional"/>
<xsd:enumeration value="all"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Ligatures">
<xsd:attribute name="val" type="ST_Ligatures" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_NumForm">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="default"/>
<xsd:enumeration value="lining"/>
<xsd:enumeration value="oldStyle"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_NumForm">
<xsd:attribute name="val" type="ST_NumForm" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_NumSpacing">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="default"/>
<xsd:enumeration value="proportional"/>
<xsd:enumeration value="tabular"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_NumSpacing">
<xsd:attribute name="val" type="ST_NumSpacing" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_StyleSet">
<xsd:attribute name="id" type="s:ST_UnsignedDecimalNumber" use="required"/>
<xsd:attribute name="val" type="ST_OnOff" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_StylisticSets">
<xsd:sequence minOccurs="0">
<xsd:element name="styleSet" minOccurs="0" maxOccurs="unbounded" type="CT_StyleSet"/>
</xsd:sequence>
</xsd:complexType>
<xsd:group name="EG_RPrOpenType">
<xsd:sequence>
<xsd:element name="ligatures" minOccurs="0" type="CT_Ligatures"/>
<xsd:element name="numForm" minOccurs="0" type="CT_NumForm"/>
<xsd:element name="numSpacing" minOccurs="0" type="CT_NumSpacing"/>
<xsd:element name="stylisticSets" minOccurs="0" type="CT_StylisticSets"/>
<xsd:element name="cntxtAlts" minOccurs="0" type="CT_OnOff"/>
</xsd:sequence>
</xsd:group>
<xsd:element name="discardImageEditingData" type="CT_OnOff"/>
<xsd:element name="defaultImageDpi" type="CT_DefaultImageDpi"/>
<xsd:complexType name="CT_DefaultImageDpi">
<xsd:attribute name="val" type="w:ST_DecimalNumber" use="required"/>
</xsd:complexType>
<xsd:element name="entityPicker" type="w:CT_Empty"/>
<xsd:complexType name="CT_SdtCheckboxSymbol">
<xsd:attribute name="font" type="s:ST_String"/>
<xsd:attribute name="val" type="w:ST_ShortHexNumber"/>
</xsd:complexType>
<xsd:complexType name="CT_SdtCheckbox">
<xsd:sequence>
<xsd:element name="checked" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="checkedState" type="CT_SdtCheckboxSymbol" minOccurs="0"/>
<xsd:element name="uncheckedState" type="CT_SdtCheckboxSymbol" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="checkbox" type="CT_SdtCheckbox"/>
</xsd:schema>
@@ -0,0 +1,67 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2012/wordml" targetNamespace="http://schemas.microsoft.com/office/word/2012/wordml">
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" schemaLocation="../ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd"/>
<xsd:element name="color" type="w12:CT_Color"/>
<xsd:simpleType name="ST_SdtAppearance">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="boundingBox"/>
<xsd:enumeration value="tags"/>
<xsd:enumeration value="hidden"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:element name="dataBinding" type="w12:CT_DataBinding"/>
<xsd:complexType name="CT_SdtAppearance">
<xsd:attribute name="val" type="ST_SdtAppearance"/>
</xsd:complexType>
<xsd:element name="appearance" type="CT_SdtAppearance"/>
<xsd:complexType name="CT_CommentsEx">
<xsd:sequence>
<xsd:element name="commentEx" type="CT_CommentEx" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_CommentEx">
<xsd:attribute name="paraId" type="w12:ST_LongHexNumber" use="required"/>
<xsd:attribute name="paraIdParent" type="w12:ST_LongHexNumber" use="optional"/>
<xsd:attribute name="done" type="s:ST_OnOff" use="optional"/>
</xsd:complexType>
<xsd:element name="commentsEx" type="CT_CommentsEx"/>
<xsd:complexType name="CT_People">
<xsd:sequence>
<xsd:element name="person" type="CT_Person" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_PresenceInfo">
<xsd:attribute name="providerId" type="xsd:string" use="required"/>
<xsd:attribute name="userId" type="xsd:string" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_Person">
<xsd:sequence>
<xsd:element name="presenceInfo" type="CT_PresenceInfo" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="author" type="s:ST_String" use="required"/>
</xsd:complexType>
<xsd:element name="people" type="CT_People"/>
<xsd:complexType name="CT_SdtRepeatedSection">
<xsd:sequence>
<xsd:element name="sectionTitle" type="w12:CT_String" minOccurs="0"/>
<xsd:element name="doNotAllowInsertDeleteSection" type="w12:CT_OnOff" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="ST_Guid">
<xsd:restriction base="xsd:token">
<xsd:pattern value="\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Guid">
<xsd:attribute name="val" type="ST_Guid"/>
</xsd:complexType>
<xsd:element name="repeatingSection" type="CT_SdtRepeatedSection"/>
<xsd:element name="repeatingSectionItem" type="w12:CT_Empty"/>
<xsd:element name="chartTrackingRefBased" type="w12:CT_OnOff"/>
<xsd:element name="collapsed" type="w12:CT_OnOff"/>
<xsd:element name="docId" type="CT_Guid"/>
<xsd:element name="footnoteColumns" type="w12:CT_DecimalNumber"/>
<xsd:element name="webExtensionLinked" type="w12:CT_OnOff"/>
<xsd:element name="webExtensionCreated" type="w12:CT_OnOff"/>
<xsd:attribute name="restartNumberingAfterBreak" type="s:ST_OnOff"/>
</xsd:schema>
@@ -0,0 +1,14 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2018/wordml" targetNamespace="http://schemas.microsoft.com/office/word/2018/wordml">
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
<xsd:complexType name="CT_Extension">
<xsd:sequence>
<xsd:any processContents="lax"/>
</xsd:sequence>
<xsd:attribute name="uri" type="xsd:token"/>
</xsd:complexType>
<xsd:complexType name="CT_ExtensionList">
<xsd:sequence>
<xsd:element name="ext" type="CT_Extension" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
@@ -0,0 +1,20 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2018/wordml/cex" targetNamespace="http://schemas.microsoft.com/office/word/2018/wordml/cex">
<xsd:import id="w16" namespace="http://schemas.microsoft.com/office/word/2018/wordml" schemaLocation="wml-2018.xsd"/>
<xsd:import id="w" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
<xsd:import id="s" namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" schemaLocation="../ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd"/>
<xsd:complexType name="CT_CommentsExtensible">
<xsd:sequence>
<xsd:element name="commentExtensible" type="CT_CommentExtensible" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="extLst" type="w16:CT_ExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_CommentExtensible">
<xsd:sequence>
<xsd:element name="extLst" type="w16:CT_ExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="durableId" type="w:ST_LongHexNumber" use="required"/>
<xsd:attribute name="dateUtc" type="w:ST_DateTime" use="optional"/>
<xsd:attribute name="intelligentPlaceholder" type="s:ST_OnOff" use="optional"/>
</xsd:complexType>
<xsd:element name="commentsExtensible" type="CT_CommentsExtensible"/>
</xsd:schema>
@@ -0,0 +1,13 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2016/wordml/cid" targetNamespace="http://schemas.microsoft.com/office/word/2016/wordml/cid">
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
<xsd:complexType name="CT_CommentsIds">
<xsd:sequence>
<xsd:element name="commentId" type="CT_CommentId" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_CommentId">
<xsd:attribute name="paraId" type="w12:ST_LongHexNumber" use="required"/>
<xsd:attribute name="durableId" type="w12:ST_LongHexNumber" use="required"/>
</xsd:complexType>
<xsd:element name="commentsIds" type="CT_CommentsIds"/>
</xsd:schema>
@@ -0,0 +1,4 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" targetNamespace="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash">
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
<xsd:attribute name="storeItemChecksum" type="w12:ST_String"/>
</xsd:schema>
@@ -0,0 +1,8 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2015/wordml/symex" targetNamespace="http://schemas.microsoft.com/office/word/2015/wordml/symex">
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
<xsd:complexType name="CT_SymEx">
<xsd:attribute name="font" type="w12:ST_String"/>
<xsd:attribute name="char" type="w12:ST_LongHexNumber"/>
</xsd:complexType>
<xsd:element name="symEx" type="CT_SymEx"/>
</xsd:schema>
@@ -0,0 +1,183 @@
"""
Helper for running LibreOffice (soffice) in environments where AF_UNIX
sockets may be blocked (e.g., sandboxed VMs). Detects the restriction
at runtime and applies an LD_PRELOAD shim if needed.
Usage:
from office.soffice import run_soffice, get_soffice_env
# Option 1 – run soffice directly
result = run_soffice(["--headless", "--convert-to", "pdf", "input.docx"])
# Option 2 – get env dict for your own subprocess calls
env = get_soffice_env()
subprocess.run(["soffice", ...], env=env)
"""
import os
import socket
import subprocess
import tempfile
from pathlib import Path
def get_soffice_env() -> dict:
env = os.environ.copy()
env["SAL_USE_VCLPLUGIN"] = "svp"
if _needs_shim():
shim = _ensure_shim()
env["LD_PRELOAD"] = str(shim)
return env
def run_soffice(args: list[str], **kwargs) -> subprocess.CompletedProcess:
env = get_soffice_env()
return subprocess.run(["soffice"] + args, env=env, **kwargs)
_SHIM_SO = Path(tempfile.gettempdir()) / "lo_socket_shim.so"
def _needs_shim() -> bool:
try:
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.close()
return False
except OSError:
return True
def _ensure_shim() -> Path:
if _SHIM_SO.exists():
return _SHIM_SO
src = Path(tempfile.gettempdir()) / "lo_socket_shim.c"
src.write_text(_SHIM_SOURCE)
subprocess.run(
["gcc", "-shared", "-fPIC", "-o", str(_SHIM_SO), str(src), "-ldl"],
check=True,
capture_output=True,
)
src.unlink()
return _SHIM_SO
_SHIM_SOURCE = r"""
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
static int (*real_socket)(int, int, int);
static int (*real_socketpair)(int, int, int, int[2]);
static int (*real_listen)(int, int);
static int (*real_accept)(int, struct sockaddr *, socklen_t *);
static int (*real_close)(int);
static int (*real_read)(int, void *, size_t);
/* Per-FD bookkeeping (FDs >= 1024 are passed through unshimmed). */
static int is_shimmed[1024];
static int peer_of[1024];
static int wake_r[1024]; /* accept() blocks reading this */
static int wake_w[1024]; /* close() writes to this */
static int listener_fd = -1; /* FD that received listen() */
__attribute__((constructor))
static void init(void) {
real_socket = dlsym(RTLD_NEXT, "socket");
real_socketpair = dlsym(RTLD_NEXT, "socketpair");
real_listen = dlsym(RTLD_NEXT, "listen");
real_accept = dlsym(RTLD_NEXT, "accept");
real_close = dlsym(RTLD_NEXT, "close");
real_read = dlsym(RTLD_NEXT, "read");
for (int i = 0; i < 1024; i++) {
peer_of[i] = -1;
wake_r[i] = -1;
wake_w[i] = -1;
}
}
/* ---- socket ---------------------------------------------------------- */
int socket(int domain, int type, int protocol) {
if (domain == AF_UNIX) {
int fd = real_socket(domain, type, protocol);
if (fd >= 0) return fd;
/* socket(AF_UNIX) blocked – fall back to socketpair(). */
int sv[2];
if (real_socketpair(domain, type, protocol, sv) == 0) {
if (sv[0] >= 0 && sv[0] < 1024) {
is_shimmed[sv[0]] = 1;
peer_of[sv[0]] = sv[1];
int wp[2];
if (pipe(wp) == 0) {
wake_r[sv[0]] = wp[0];
wake_w[sv[0]] = wp[1];
}
}
return sv[0];
}
errno = EPERM;
return -1;
}
return real_socket(domain, type, protocol);
}
/* ---- listen ---------------------------------------------------------- */
int listen(int sockfd, int backlog) {
if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) {
listener_fd = sockfd;
return 0;
}
return real_listen(sockfd, backlog);
}
/* ---- accept ---------------------------------------------------------- */
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) {
/* Block until close() writes to the wake pipe. */
if (wake_r[sockfd] >= 0) {
char buf;
real_read(wake_r[sockfd], &buf, 1);
}
errno = ECONNABORTED;
return -1;
}
return real_accept(sockfd, addr, addrlen);
}
/* ---- close ----------------------------------------------------------- */
int close(int fd) {
if (fd >= 0 && fd < 1024 && is_shimmed[fd]) {
int was_listener = (fd == listener_fd);
is_shimmed[fd] = 0;
if (wake_w[fd] >= 0) { /* unblock accept() */
char c = 0;
write(wake_w[fd], &c, 1);
real_close(wake_w[fd]);
wake_w[fd] = -1;
}
if (wake_r[fd] >= 0) { real_close(wake_r[fd]); wake_r[fd] = -1; }
if (peer_of[fd] >= 0) { real_close(peer_of[fd]); peer_of[fd] = -1; }
if (was_listener)
_exit(0); /* conversion done – exit */
}
return real_close(fd);
}
"""
if __name__ == "__main__":
import sys
result = run_soffice(sys.argv[1:])
sys.exit(result.returncode)
@@ -0,0 +1,132 @@
"""Unpack Office files (DOCX, PPTX, XLSX) for editing.
Extracts the ZIP archive, pretty-prints XML files, and optionally:
- Merges adjacent runs with identical formatting (DOCX only)
- Simplifies adjacent tracked changes from same author (DOCX only)
Usage:
python unpack.py <office_file> <output_dir> [options]
Examples:
python unpack.py document.docx unpacked/
python unpack.py presentation.pptx unpacked/
python unpack.py document.docx unpacked/ --merge-runs false
"""
import argparse
import sys
import zipfile
from pathlib import Path
import defusedxml.minidom
from helpers.merge_runs import merge_runs as do_merge_runs
from helpers.simplify_redlines import simplify_redlines as do_simplify_redlines
SMART_QUOTE_REPLACEMENTS = {
"\u201c": "&#x201C;",
"\u201d": "&#x201D;",
"\u2018": "&#x2018;",
"\u2019": "&#x2019;",
}
def unpack(
input_file: str,
output_directory: str,
merge_runs: bool = True,
simplify_redlines: bool = True,
) -> tuple[None, str]:
input_path = Path(input_file)
output_path = Path(output_directory)
suffix = input_path.suffix.lower()
if not input_path.exists():
return None, f"Error: {input_file} does not exist"
if suffix not in {".docx", ".pptx", ".xlsx"}:
return None, f"Error: {input_file} must be a .docx, .pptx, or .xlsx file"
try:
output_path.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(input_path, "r") as zf:
zf.extractall(output_path)
xml_files = list(output_path.rglob("*.xml")) + list(output_path.rglob("*.rels"))
for xml_file in xml_files:
_pretty_print_xml(xml_file)
message = f"Unpacked {input_file} ({len(xml_files)} XML files)"
if suffix == ".docx":
if simplify_redlines:
simplify_count, _ = do_simplify_redlines(str(output_path))
message += f", simplified {simplify_count} tracked changes"
if merge_runs:
merge_count, _ = do_merge_runs(str(output_path))
message += f", merged {merge_count} runs"
for xml_file in xml_files:
_escape_smart_quotes(xml_file)
return None, message
except zipfile.BadZipFile:
return None, f"Error: {input_file} is not a valid Office file"
except Exception as e:
return None, f"Error unpacking: {e}"
def _pretty_print_xml(xml_file: Path) -> None:
try:
content = xml_file.read_text(encoding="utf-8")
dom = defusedxml.minidom.parseString(content)
xml_file.write_bytes(dom.toprettyxml(indent=" ", encoding="utf-8"))
except Exception:
pass
def _escape_smart_quotes(xml_file: Path) -> None:
try:
content = xml_file.read_text(encoding="utf-8")
for char, entity in SMART_QUOTE_REPLACEMENTS.items():
content = content.replace(char, entity)
xml_file.write_text(content, encoding="utf-8")
except Exception:
pass
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Unpack an Office file (DOCX, PPTX, XLSX) for editing"
)
parser.add_argument("input_file", help="Office file to unpack")
parser.add_argument("output_directory", help="Output directory")
parser.add_argument(
"--merge-runs",
type=lambda x: x.lower() == "true",
default=True,
metavar="true|false",
help="Merge adjacent runs with identical formatting (DOCX only, default: true)",
)
parser.add_argument(
"--simplify-redlines",
type=lambda x: x.lower() == "true",
default=True,
metavar="true|false",
help="Merge adjacent tracked changes from same author (DOCX only, default: true)",
)
args = parser.parse_args()
_, message = unpack(
args.input_file,
args.output_directory,
merge_runs=args.merge_runs,
simplify_redlines=args.simplify_redlines,
)
print(message)
if "Error" in message:
sys.exit(1)
@@ -0,0 +1,111 @@
"""
Command line tool to validate Office document XML files against XSD schemas and tracked changes.
Usage:
python validate.py <path> [--original <original_file>] [--auto-repair] [--author NAME]
The first argument can be either:
- An unpacked directory containing the Office document XML files
- A packed Office file (.docx/.pptx/.xlsx) which will be unpacked to a temp directory
Auto-repair fixes:
- paraId/durableId values that exceed OOXML limits
- Missing xml:space="preserve" on w:t elements with whitespace
"""
import argparse
import sys
import tempfile
import zipfile
from pathlib import Path
from validators import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator
def main():
parser = argparse.ArgumentParser(description="Validate Office document XML files")
parser.add_argument(
"path",
help="Path to unpacked directory or packed Office file (.docx/.pptx/.xlsx)",
)
parser.add_argument(
"--original",
required=False,
default=None,
help="Path to original file (.docx/.pptx/.xlsx). If omitted, all XSD errors are reported and redlining validation is skipped.",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="Enable verbose output",
)
parser.add_argument(
"--auto-repair",
action="store_true",
help="Automatically repair common issues (hex IDs, whitespace preservation)",
)
parser.add_argument(
"--author",
default="Claude",
help="Author name for redlining validation (default: Claude)",
)
args = parser.parse_args()
path = Path(args.path)
assert path.exists(), f"Error: {path} does not exist"
original_file = None
if args.original:
original_file = Path(args.original)
assert original_file.is_file(), f"Error: {original_file} is not a file"
assert original_file.suffix.lower() in [".docx", ".pptx", ".xlsx"], (
f"Error: {original_file} must be a .docx, .pptx, or .xlsx file"
)
file_extension = (original_file or path).suffix.lower()
assert file_extension in [".docx", ".pptx", ".xlsx"], (
f"Error: Cannot determine file type from {path}. Use --original or provide a .docx/.pptx/.xlsx file."
)
if path.is_file() and path.suffix.lower() in [".docx", ".pptx", ".xlsx"]:
temp_dir = tempfile.mkdtemp()
with zipfile.ZipFile(path, "r") as zf:
zf.extractall(temp_dir)
unpacked_dir = Path(temp_dir)
else:
assert path.is_dir(), f"Error: {path} is not a directory or Office file"
unpacked_dir = path
match file_extension:
case ".docx":
validators = [
DOCXSchemaValidator(unpacked_dir, original_file, verbose=args.verbose),
]
if original_file:
validators.append(
RedliningValidator(unpacked_dir, original_file, verbose=args.verbose, author=args.author)
)
case ".pptx":
validators = [
PPTXSchemaValidator(unpacked_dir, original_file, verbose=args.verbose),
]
case _:
print(f"Error: Validation not supported for file type {file_extension}")
sys.exit(1)
if args.auto_repair:
total_repairs = sum(v.repair() for v in validators)
if total_repairs:
print(f"Auto-repaired {total_repairs} issue(s)")
success = all(v.validate() for v in validators)
if success:
print("All validations PASSED!")
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,15 @@
"""
Validation modules for Word document processing.
"""
from .base import BaseSchemaValidator
from .docx import DOCXSchemaValidator
from .pptx import PPTXSchemaValidator
from .redlining import RedliningValidator
__all__ = [
"BaseSchemaValidator",
"DOCXSchemaValidator",
"PPTXSchemaValidator",
"RedliningValidator",
]
@@ -0,0 +1,847 @@
"""
Base validator with common validation logic for document files.
"""
import re
from pathlib import Path
import defusedxml.minidom
import lxml.etree
class BaseSchemaValidator:
IGNORED_VALIDATION_ERRORS = [
"hyphenationZone",
"purl.org/dc/terms",
]
UNIQUE_ID_REQUIREMENTS = {
"comment": ("id", "file"),
"commentrangestart": ("id", "file"),
"commentrangeend": ("id", "file"),
"bookmarkstart": ("id", "file"),
"bookmarkend": ("id", "file"),
"sldid": ("id", "file"),
"sldmasterid": ("id", "global"),
"sldlayoutid": ("id", "global"),
"cm": ("authorid", "file"),
"sheet": ("sheetid", "file"),
"definedname": ("id", "file"),
"cxnsp": ("id", "file"),
"sp": ("id", "file"),
"pic": ("id", "file"),
"grpsp": ("id", "file"),
}
EXCLUDED_ID_CONTAINERS = {
"sectionlst",
}
ELEMENT_RELATIONSHIP_TYPES = {}
SCHEMA_MAPPINGS = {
"word": "ISO-IEC29500-4_2016/wml.xsd",
"ppt": "ISO-IEC29500-4_2016/pml.xsd",
"xl": "ISO-IEC29500-4_2016/sml.xsd",
"[Content_Types].xml": "ecma/fouth-edition/opc-contentTypes.xsd",
"app.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd",
"core.xml": "ecma/fouth-edition/opc-coreProperties.xsd",
"custom.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd",
".rels": "ecma/fouth-edition/opc-relationships.xsd",
"people.xml": "microsoft/wml-2012.xsd",
"commentsIds.xml": "microsoft/wml-cid-2016.xsd",
"commentsExtensible.xml": "microsoft/wml-cex-2018.xsd",
"commentsExtended.xml": "microsoft/wml-2012.xsd",
"chart": "ISO-IEC29500-4_2016/dml-chart.xsd",
"theme": "ISO-IEC29500-4_2016/dml-main.xsd",
"drawing": "ISO-IEC29500-4_2016/dml-main.xsd",
}
MC_NAMESPACE = "http://schemas.openxmlformats.org/markup-compatibility/2006"
XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace"
PACKAGE_RELATIONSHIPS_NAMESPACE = (
"http://schemas.openxmlformats.org/package/2006/relationships"
)
OFFICE_RELATIONSHIPS_NAMESPACE = (
"http://schemas.openxmlformats.org/officeDocument/2006/relationships"
)
CONTENT_TYPES_NAMESPACE = (
"http://schemas.openxmlformats.org/package/2006/content-types"
)
MAIN_CONTENT_FOLDERS = {"word", "ppt", "xl"}
OOXML_NAMESPACES = {
"http://schemas.openxmlformats.org/officeDocument/2006/math",
"http://schemas.openxmlformats.org/officeDocument/2006/relationships",
"http://schemas.openxmlformats.org/schemaLibrary/2006/main",
"http://schemas.openxmlformats.org/drawingml/2006/main",
"http://schemas.openxmlformats.org/drawingml/2006/chart",
"http://schemas.openxmlformats.org/drawingml/2006/chartDrawing",
"http://schemas.openxmlformats.org/drawingml/2006/diagram",
"http://schemas.openxmlformats.org/drawingml/2006/picture",
"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing",
"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"http://schemas.openxmlformats.org/presentationml/2006/main",
"http://schemas.openxmlformats.org/spreadsheetml/2006/main",
"http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes",
"http://www.w3.org/XML/1998/namespace",
}
def __init__(self, unpacked_dir, original_file=None, verbose=False):
self.unpacked_dir = Path(unpacked_dir).resolve()
self.original_file = Path(original_file) if original_file else None
self.verbose = verbose
self.schemas_dir = Path(__file__).parent.parent / "schemas"
patterns = ["*.xml", "*.rels"]
self.xml_files = [
f for pattern in patterns for f in self.unpacked_dir.rglob(pattern)
]
if not self.xml_files:
print(f"Warning: No XML files found in {self.unpacked_dir}")
def validate(self):
raise NotImplementedError("Subclasses must implement the validate method")
def repair(self) -> int:
return self.repair_whitespace_preservation()
def repair_whitespace_preservation(self) -> int:
repairs = 0
for xml_file in self.xml_files:
try:
content = xml_file.read_text(encoding="utf-8")
dom = defusedxml.minidom.parseString(content)
modified = False
for elem in dom.getElementsByTagName("*"):
if elem.tagName.endswith(":t") and elem.firstChild:
text = elem.firstChild.nodeValue
if text and (text.startswith((' ', '\t')) or text.endswith((' ', '\t'))):
if elem.getAttribute("xml:space") != "preserve":
elem.setAttribute("xml:space", "preserve")
text_preview = repr(text[:30]) + "..." if len(text) > 30 else repr(text)
print(f" Repaired: {xml_file.name}: Added xml:space='preserve' to {elem.tagName}: {text_preview}")
repairs += 1
modified = True
if modified:
xml_file.write_bytes(dom.toxml(encoding="UTF-8"))
except Exception:
pass
return repairs
def validate_xml(self):
errors = []
for xml_file in self.xml_files:
try:
lxml.etree.parse(str(xml_file))
except lxml.etree.XMLSyntaxError as e:
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {e.lineno}: {e.msg}"
)
except Exception as e:
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Unexpected error: {str(e)}"
)
if errors:
print(f"FAILED - Found {len(errors)} XML violations:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - All XML files are well-formed")
return True
def validate_namespaces(self):
errors = []
for xml_file in self.xml_files:
try:
root = lxml.etree.parse(str(xml_file)).getroot()
declared = set(root.nsmap.keys()) - {None}
for attr_val in [
v for k, v in root.attrib.items() if k.endswith("Ignorable")
]:
undeclared = set(attr_val.split()) - declared
errors.extend(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Namespace '{ns}' in Ignorable but not declared"
for ns in undeclared
)
except lxml.etree.XMLSyntaxError:
continue
if errors:
print(f"FAILED - {len(errors)} namespace issues:")
for error in errors:
print(error)
return False
if self.verbose:
print("PASSED - All namespace prefixes properly declared")
return True
def validate_unique_ids(self):
errors = []
global_ids = {}
for xml_file in self.xml_files:
try:
root = lxml.etree.parse(str(xml_file)).getroot()
file_ids = {}
mc_elements = root.xpath(
".//mc:AlternateContent", namespaces={"mc": self.MC_NAMESPACE}
)
for elem in mc_elements:
elem.getparent().remove(elem)
for elem in root.iter():
tag = (
elem.tag.split("}")[-1].lower()
if "}" in elem.tag
else elem.tag.lower()
)
if tag in self.UNIQUE_ID_REQUIREMENTS:
in_excluded_container = any(
ancestor.tag.split("}")[-1].lower() in self.EXCLUDED_ID_CONTAINERS
for ancestor in elem.iterancestors()
)
if in_excluded_container:
continue
attr_name, scope = self.UNIQUE_ID_REQUIREMENTS[tag]
id_value = None
for attr, value in elem.attrib.items():
attr_local = (
attr.split("}")[-1].lower()
if "}" in attr
else attr.lower()
)
if attr_local == attr_name:
id_value = value
break
if id_value is not None:
if scope == "global":
if id_value in global_ids:
prev_file, prev_line, prev_tag = global_ids[
id_value
]
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {elem.sourceline}: Global ID '{id_value}' in <{tag}> "
f"already used in {prev_file} at line {prev_line} in <{prev_tag}>"
)
else:
global_ids[id_value] = (
xml_file.relative_to(self.unpacked_dir),
elem.sourceline,
tag,
)
elif scope == "file":
key = (tag, attr_name)
if key not in file_ids:
file_ids[key] = {}
if id_value in file_ids[key]:
prev_line = file_ids[key][id_value]
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {elem.sourceline}: Duplicate {attr_name}='{id_value}' in <{tag}> "
f"(first occurrence at line {prev_line})"
)
else:
file_ids[key][id_value] = elem.sourceline
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
)
if errors:
print(f"FAILED - Found {len(errors)} ID uniqueness violations:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - All required IDs are unique")
return True
def validate_file_references(self):
errors = []
rels_files = list(self.unpacked_dir.rglob("*.rels"))
if not rels_files:
if self.verbose:
print("PASSED - No .rels files found")
return True
all_files = []
for file_path in self.unpacked_dir.rglob("*"):
if (
file_path.is_file()
and file_path.name != "[Content_Types].xml"
and not file_path.name.endswith(".rels")
):
all_files.append(file_path.resolve())
all_referenced_files = set()
if self.verbose:
print(
f"Found {len(rels_files)} .rels files and {len(all_files)} target files"
)
for rels_file in rels_files:
try:
rels_root = lxml.etree.parse(str(rels_file)).getroot()
rels_dir = rels_file.parent
referenced_files = set()
broken_refs = []
for rel in rels_root.findall(
".//ns:Relationship",
namespaces={"ns": self.PACKAGE_RELATIONSHIPS_NAMESPACE},
):
target = rel.get("Target")
if target and not target.startswith(
("http", "mailto:")
):
if target.startswith("/"):
target_path = self.unpacked_dir / target.lstrip("/")
elif rels_file.name == ".rels":
target_path = self.unpacked_dir / target
else:
base_dir = rels_dir.parent
target_path = base_dir / target
try:
target_path = target_path.resolve()
if target_path.exists() and target_path.is_file():
referenced_files.add(target_path)
all_referenced_files.add(target_path)
else:
broken_refs.append((target, rel.sourceline))
except (OSError, ValueError):
broken_refs.append((target, rel.sourceline))
if broken_refs:
rel_path = rels_file.relative_to(self.unpacked_dir)
for broken_ref, line_num in broken_refs:
errors.append(
f" {rel_path}: Line {line_num}: Broken reference to {broken_ref}"
)
except Exception as e:
rel_path = rels_file.relative_to(self.unpacked_dir)
errors.append(f" Error parsing {rel_path}: {e}")
unreferenced_files = set(all_files) - all_referenced_files
if unreferenced_files:
for unref_file in sorted(unreferenced_files):
unref_rel_path = unref_file.relative_to(self.unpacked_dir)
errors.append(f" Unreferenced file: {unref_rel_path}")
if errors:
print(f"FAILED - Found {len(errors)} relationship validation errors:")
for error in errors:
print(error)
print(
"CRITICAL: These errors will cause the document to appear corrupt. "
+ "Broken references MUST be fixed, "
+ "and unreferenced files MUST be referenced or removed."
)
return False
else:
if self.verbose:
print(
"PASSED - All references are valid and all files are properly referenced"
)
return True
def validate_all_relationship_ids(self):
import lxml.etree
errors = []
for xml_file in self.xml_files:
if xml_file.suffix == ".rels":
continue
rels_dir = xml_file.parent / "_rels"
rels_file = rels_dir / f"{xml_file.name}.rels"
if not rels_file.exists():
continue
try:
rels_root = lxml.etree.parse(str(rels_file)).getroot()
rid_to_type = {}
for rel in rels_root.findall(
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
):
rid = rel.get("Id")
rel_type = rel.get("Type", "")
if rid:
if rid in rid_to_type:
rels_rel_path = rels_file.relative_to(self.unpacked_dir)
errors.append(
f" {rels_rel_path}: Line {rel.sourceline}: "
f"Duplicate relationship ID '{rid}' (IDs must be unique)"
)
type_name = (
rel_type.split("/")[-1] if "/" in rel_type else rel_type
)
rid_to_type[rid] = type_name
xml_root = lxml.etree.parse(str(xml_file)).getroot()
r_ns = self.OFFICE_RELATIONSHIPS_NAMESPACE
rid_attrs_to_check = ["id", "embed", "link"]
for elem in xml_root.iter():
for attr_name in rid_attrs_to_check:
rid_attr = elem.get(f"{{{r_ns}}}{attr_name}")
if not rid_attr:
continue
xml_rel_path = xml_file.relative_to(self.unpacked_dir)
elem_name = (
elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag
)
if rid_attr not in rid_to_type:
errors.append(
f" {xml_rel_path}: Line {elem.sourceline}: "
f"<{elem_name}> r:{attr_name} references non-existent relationship '{rid_attr}' "
f"(valid IDs: {', '.join(sorted(rid_to_type.keys())[:5])}{'...' if len(rid_to_type) > 5 else ''})"
)
elif attr_name == "id" and self.ELEMENT_RELATIONSHIP_TYPES:
expected_type = self._get_expected_relationship_type(
elem_name
)
if expected_type:
actual_type = rid_to_type[rid_attr]
if expected_type not in actual_type.lower():
errors.append(
f" {xml_rel_path}: Line {elem.sourceline}: "
f"<{elem_name}> references '{rid_attr}' which points to '{actual_type}' "
f"but should point to a '{expected_type}' relationship"
)
except Exception as e:
xml_rel_path = xml_file.relative_to(self.unpacked_dir)
errors.append(f" Error processing {xml_rel_path}: {e}")
if errors:
print(f"FAILED - Found {len(errors)} relationship ID reference errors:")
for error in errors:
print(error)
print("\nThese ID mismatches will cause the document to appear corrupt!")
return False
else:
if self.verbose:
print("PASSED - All relationship ID references are valid")
return True
def _get_expected_relationship_type(self, element_name):
elem_lower = element_name.lower()
if elem_lower in self.ELEMENT_RELATIONSHIP_TYPES:
return self.ELEMENT_RELATIONSHIP_TYPES[elem_lower]
if elem_lower.endswith("id") and len(elem_lower) > 2:
prefix = elem_lower[:-2]
if prefix.endswith("master"):
return prefix.lower()
elif prefix.endswith("layout"):
return prefix.lower()
else:
if prefix == "sld":
return "slide"
return prefix.lower()
if elem_lower.endswith("reference") and len(elem_lower) > 9:
prefix = elem_lower[:-9]
return prefix.lower()
return None
def validate_content_types(self):
errors = []
content_types_file = self.unpacked_dir / "[Content_Types].xml"
if not content_types_file.exists():
print("FAILED - [Content_Types].xml file not found")
return False
try:
root = lxml.etree.parse(str(content_types_file)).getroot()
declared_parts = set()
declared_extensions = set()
for override in root.findall(
f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Override"
):
part_name = override.get("PartName")
if part_name is not None:
declared_parts.add(part_name.lstrip("/"))
for default in root.findall(
f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Default"
):
extension = default.get("Extension")
if extension is not None:
declared_extensions.add(extension.lower())
declarable_roots = {
"sld",
"sldLayout",
"sldMaster",
"presentation",
"document",
"workbook",
"worksheet",
"theme",
}
media_extensions = {
"png": "image/png",
"jpg": "image/jpeg",
"jpeg": "image/jpeg",
"gif": "image/gif",
"bmp": "image/bmp",
"tiff": "image/tiff",
"wmf": "image/x-wmf",
"emf": "image/x-emf",
}
all_files = list(self.unpacked_dir.rglob("*"))
all_files = [f for f in all_files if f.is_file()]
for xml_file in self.xml_files:
path_str = str(xml_file.relative_to(self.unpacked_dir)).replace(
"\\", "/"
)
if any(
skip in path_str
for skip in [".rels", "[Content_Types]", "docProps/", "_rels/"]
):
continue
try:
root_tag = lxml.etree.parse(str(xml_file)).getroot().tag
root_name = root_tag.split("}")[-1] if "}" in root_tag else root_tag
if root_name in declarable_roots and path_str not in declared_parts:
errors.append(
f" {path_str}: File with <{root_name}> root not declared in [Content_Types].xml"
)
except Exception:
continue
for file_path in all_files:
if file_path.suffix.lower() in {".xml", ".rels"}:
continue
if file_path.name == "[Content_Types].xml":
continue
if "_rels" in file_path.parts or "docProps" in file_path.parts:
continue
extension = file_path.suffix.lstrip(".").lower()
if extension and extension not in declared_extensions:
if extension in media_extensions:
relative_path = file_path.relative_to(self.unpacked_dir)
errors.append(
f' {relative_path}: File with extension \'{extension}\' not declared in [Content_Types].xml - should add: <Default Extension="{extension}" ContentType="{media_extensions[extension]}"/>'
)
except Exception as e:
errors.append(f" Error parsing [Content_Types].xml: {e}")
if errors:
print(f"FAILED - Found {len(errors)} content type declaration errors:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print(
"PASSED - All content files are properly declared in [Content_Types].xml"
)
return True
def validate_file_against_xsd(self, xml_file, verbose=False):
xml_file = Path(xml_file).resolve()
unpacked_dir = self.unpacked_dir.resolve()
is_valid, current_errors = self._validate_single_file_xsd(
xml_file, unpacked_dir
)
if is_valid is None:
return None, set()
elif is_valid:
return True, set()
original_errors = self._get_original_file_errors(xml_file)
assert current_errors is not None
new_errors = current_errors - original_errors
new_errors = {
e for e in new_errors
if not any(pattern in e for pattern in self.IGNORED_VALIDATION_ERRORS)
}
if new_errors:
if verbose:
relative_path = xml_file.relative_to(unpacked_dir)
print(f"FAILED - {relative_path}: {len(new_errors)} new error(s)")
for error in list(new_errors)[:3]:
truncated = error[:250] + "..." if len(error) > 250 else error
print(f" - {truncated}")
return False, new_errors
else:
if verbose:
print(
f"PASSED - No new errors (original had {len(current_errors)} errors)"
)
return True, set()
def validate_against_xsd(self):
new_errors = []
original_error_count = 0
valid_count = 0
skipped_count = 0
for xml_file in self.xml_files:
relative_path = str(xml_file.relative_to(self.unpacked_dir))
is_valid, new_file_errors = self.validate_file_against_xsd(
xml_file, verbose=False
)
if is_valid is None:
skipped_count += 1
continue
elif is_valid and not new_file_errors:
valid_count += 1
continue
elif is_valid:
original_error_count += 1
valid_count += 1
continue
new_errors.append(f" {relative_path}: {len(new_file_errors)} new error(s)")
for error in list(new_file_errors)[:3]:
new_errors.append(
f" - {error[:250]}..." if len(error) > 250 else f" - {error}"
)
if self.verbose:
print(f"Validated {len(self.xml_files)} files:")
print(f" - Valid: {valid_count}")
print(f" - Skipped (no schema): {skipped_count}")
if original_error_count:
print(f" - With original errors (ignored): {original_error_count}")
print(
f" - With NEW errors: {len(new_errors) > 0 and len([e for e in new_errors if not e.startswith(' ')]) or 0}"
)
if new_errors:
print("\nFAILED - Found NEW validation errors:")
for error in new_errors:
print(error)
return False
else:
if self.verbose:
print("\nPASSED - No new XSD validation errors introduced")
return True
def _get_schema_path(self, xml_file):
if xml_file.name in self.SCHEMA_MAPPINGS:
return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.name]
if xml_file.suffix == ".rels":
return self.schemas_dir / self.SCHEMA_MAPPINGS[".rels"]
if "charts/" in str(xml_file) and xml_file.name.startswith("chart"):
return self.schemas_dir / self.SCHEMA_MAPPINGS["chart"]
if "theme/" in str(xml_file) and xml_file.name.startswith("theme"):
return self.schemas_dir / self.SCHEMA_MAPPINGS["theme"]
if xml_file.parent.name in self.MAIN_CONTENT_FOLDERS:
return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.parent.name]
return None
def _clean_ignorable_namespaces(self, xml_doc):
xml_string = lxml.etree.tostring(xml_doc, encoding="unicode")
xml_copy = lxml.etree.fromstring(xml_string)
for elem in xml_copy.iter():
attrs_to_remove = []
for attr in elem.attrib:
if "{" in attr:
ns = attr.split("}")[0][1:]
if ns not in self.OOXML_NAMESPACES:
attrs_to_remove.append(attr)
for attr in attrs_to_remove:
del elem.attrib[attr]
self._remove_ignorable_elements(xml_copy)
return lxml.etree.ElementTree(xml_copy)
def _remove_ignorable_elements(self, root):
elements_to_remove = []
for elem in list(root):
if not hasattr(elem, "tag") or callable(elem.tag):
continue
tag_str = str(elem.tag)
if tag_str.startswith("{"):
ns = tag_str.split("}")[0][1:]
if ns not in self.OOXML_NAMESPACES:
elements_to_remove.append(elem)
continue
self._remove_ignorable_elements(elem)
for elem in elements_to_remove:
root.remove(elem)
def _preprocess_for_mc_ignorable(self, xml_doc):
root = xml_doc.getroot()
if f"{{{self.MC_NAMESPACE}}}Ignorable" in root.attrib:
del root.attrib[f"{{{self.MC_NAMESPACE}}}Ignorable"]
return xml_doc
def _validate_single_file_xsd(self, xml_file, base_path):
schema_path = self._get_schema_path(xml_file)
if not schema_path:
return None, None
try:
with open(schema_path, "rb") as xsd_file:
parser = lxml.etree.XMLParser()
xsd_doc = lxml.etree.parse(
xsd_file, parser=parser, base_url=str(schema_path)
)
schema = lxml.etree.XMLSchema(xsd_doc)
with open(xml_file, "r") as f:
xml_doc = lxml.etree.parse(f)
xml_doc, _ = self._remove_template_tags_from_text_nodes(xml_doc)
xml_doc = self._preprocess_for_mc_ignorable(xml_doc)
relative_path = xml_file.relative_to(base_path)
if (
relative_path.parts
and relative_path.parts[0] in self.MAIN_CONTENT_FOLDERS
):
xml_doc = self._clean_ignorable_namespaces(xml_doc)
if schema.validate(xml_doc):
return True, set()
else:
errors = set()
for error in schema.error_log:
errors.add(error.message)
return False, errors
except Exception as e:
return False, {str(e)}
def _get_original_file_errors(self, xml_file):
if self.original_file is None:
return set()
import tempfile
import zipfile
xml_file = Path(xml_file).resolve()
unpacked_dir = self.unpacked_dir.resolve()
relative_path = xml_file.relative_to(unpacked_dir)
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
with zipfile.ZipFile(self.original_file, "r") as zip_ref:
zip_ref.extractall(temp_path)
original_xml_file = temp_path / relative_path
if not original_xml_file.exists():
return set()
is_valid, errors = self._validate_single_file_xsd(
original_xml_file, temp_path
)
return errors if errors else set()
def _remove_template_tags_from_text_nodes(self, xml_doc):
warnings = []
template_pattern = re.compile(r"\{\{[^}]*\}\}")
xml_string = lxml.etree.tostring(xml_doc, encoding="unicode")
xml_copy = lxml.etree.fromstring(xml_string)
def process_text_content(text, content_type):
if not text:
return text
matches = list(template_pattern.finditer(text))
if matches:
for match in matches:
warnings.append(
f"Found template tag in {content_type}: {match.group()}"
)
return template_pattern.sub("", text)
return text
for elem in xml_copy.iter():
if not hasattr(elem, "tag") or callable(elem.tag):
continue
tag_str = str(elem.tag)
if tag_str.endswith("}t") or tag_str == "t":
continue
elem.text = process_text_content(elem.text, "text content")
elem.tail = process_text_content(elem.tail, "tail content")
return lxml.etree.ElementTree(xml_copy), warnings
if __name__ == "__main__":
raise RuntimeError("This module should not be run directly.")
@@ -0,0 +1,446 @@
"""
Validator for Word document XML files against XSD schemas.
"""
import random
import re
import tempfile
import zipfile
import defusedxml.minidom
import lxml.etree
from .base import BaseSchemaValidator
class DOCXSchemaValidator(BaseSchemaValidator):
WORD_2006_NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
W14_NAMESPACE = "http://schemas.microsoft.com/office/word/2010/wordml"
W16CID_NAMESPACE = "http://schemas.microsoft.com/office/word/2016/wordml/cid"
ELEMENT_RELATIONSHIP_TYPES = {}
def validate(self):
if not self.validate_xml():
return False
all_valid = True
if not self.validate_namespaces():
all_valid = False
if not self.validate_unique_ids():
all_valid = False
if not self.validate_file_references():
all_valid = False
if not self.validate_content_types():
all_valid = False
if not self.validate_against_xsd():
all_valid = False
if not self.validate_whitespace_preservation():
all_valid = False
if not self.validate_deletions():
all_valid = False
if not self.validate_insertions():
all_valid = False
if not self.validate_all_relationship_ids():
all_valid = False
if not self.validate_id_constraints():
all_valid = False
if not self.validate_comment_markers():
all_valid = False
self.compare_paragraph_counts()
return all_valid
def validate_whitespace_preservation(self):
errors = []
for xml_file in self.xml_files:
if xml_file.name != "document.xml":
continue
try:
root = lxml.etree.parse(str(xml_file)).getroot()
for elem in root.iter(f"{{{self.WORD_2006_NAMESPACE}}}t"):
if elem.text:
text = elem.text
if re.search(r"^[ \t\n\r]", text) or re.search(
r"[ \t\n\r]$", text
):
xml_space_attr = f"{{{self.XML_NAMESPACE}}}space"
if (
xml_space_attr not in elem.attrib
or elem.attrib[xml_space_attr] != "preserve"
):
text_preview = (
repr(text)[:50] + "..."
if len(repr(text)) > 50
else repr(text)
)
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {elem.sourceline}: w:t element with whitespace missing xml:space='preserve': {text_preview}"
)
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
)
if errors:
print(f"FAILED - Found {len(errors)} whitespace preservation violations:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - All whitespace is properly preserved")
return True
def validate_deletions(self):
errors = []
for xml_file in self.xml_files:
if xml_file.name != "document.xml":
continue
try:
root = lxml.etree.parse(str(xml_file)).getroot()
namespaces = {"w": self.WORD_2006_NAMESPACE}
for t_elem in root.xpath(".//w:del//w:t", namespaces=namespaces):
if t_elem.text:
text_preview = (
repr(t_elem.text)[:50] + "..."
if len(repr(t_elem.text)) > 50
else repr(t_elem.text)
)
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {t_elem.sourceline}: <w:t> found within <w:del>: {text_preview}"
)
for instr_elem in root.xpath(
".//w:del//w:instrText", namespaces=namespaces
):
text_preview = (
repr(instr_elem.text or "")[:50] + "..."
if len(repr(instr_elem.text or "")) > 50
else repr(instr_elem.text or "")
)
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {instr_elem.sourceline}: <w:instrText> found within <w:del> (use <w:delInstrText>): {text_preview}"
)
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
)
if errors:
print(f"FAILED - Found {len(errors)} deletion validation violations:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - No w:t elements found within w:del elements")
return True
def count_paragraphs_in_unpacked(self):
count = 0
for xml_file in self.xml_files:
if xml_file.name != "document.xml":
continue
try:
root = lxml.etree.parse(str(xml_file)).getroot()
paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p")
count = len(paragraphs)
except Exception as e:
print(f"Error counting paragraphs in unpacked document: {e}")
return count
def count_paragraphs_in_original(self):
original = self.original_file
if original is None:
return 0
count = 0
try:
with tempfile.TemporaryDirectory() as temp_dir:
with zipfile.ZipFile(original, "r") as zip_ref:
zip_ref.extractall(temp_dir)
doc_xml_path = temp_dir + "/word/document.xml"
root = lxml.etree.parse(doc_xml_path).getroot()
paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p")
count = len(paragraphs)
except Exception as e:
print(f"Error counting paragraphs in original document: {e}")
return count
def validate_insertions(self):
errors = []
for xml_file in self.xml_files:
if xml_file.name != "document.xml":
continue
try:
root = lxml.etree.parse(str(xml_file)).getroot()
namespaces = {"w": self.WORD_2006_NAMESPACE}
invalid_elements = root.xpath(
".//w:ins//w:delText[not(ancestor::w:del)]", namespaces=namespaces
)
for elem in invalid_elements:
text_preview = (
repr(elem.text or "")[:50] + "..."
if len(repr(elem.text or "")) > 50
else repr(elem.text or "")
)
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {elem.sourceline}: <w:delText> within <w:ins>: {text_preview}"
)
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
)
if errors:
print(f"FAILED - Found {len(errors)} insertion validation violations:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - No w:delText elements within w:ins elements")
return True
def compare_paragraph_counts(self):
original_count = self.count_paragraphs_in_original()
new_count = self.count_paragraphs_in_unpacked()
diff = new_count - original_count
diff_str = f"+{diff}" if diff > 0 else str(diff)
print(f"\nParagraphs: {original_count} → {new_count} ({diff_str})")
def _parse_id_value(self, val: str, base: int = 16) -> int:
return int(val, base)
def validate_id_constraints(self):
errors = []
para_id_attr = f"{{{self.W14_NAMESPACE}}}paraId"
durable_id_attr = f"{{{self.W16CID_NAMESPACE}}}durableId"
for xml_file in self.xml_files:
try:
for elem in lxml.etree.parse(str(xml_file)).iter():
if val := elem.get(para_id_attr):
if self._parse_id_value(val, base=16) >= 0x80000000:
errors.append(
f" {xml_file.name}:{elem.sourceline}: paraId={val} >= 0x80000000"
)
if val := elem.get(durable_id_attr):
if xml_file.name == "numbering.xml":
try:
if self._parse_id_value(val, base=10) >= 0x7FFFFFFF:
errors.append(
f" {xml_file.name}:{elem.sourceline}: "
f"durableId={val} >= 0x7FFFFFFF"
)
except ValueError:
errors.append(
f" {xml_file.name}:{elem.sourceline}: "
f"durableId={val} must be decimal in numbering.xml"
)
else:
if self._parse_id_value(val, base=16) >= 0x7FFFFFFF:
errors.append(
f" {xml_file.name}:{elem.sourceline}: "
f"durableId={val} >= 0x7FFFFFFF"
)
except Exception:
pass
if errors:
print(f"FAILED - {len(errors)} ID constraint violations:")
for e in errors:
print(e)
elif self.verbose:
print("PASSED - All paraId/durableId values within constraints")
return not errors
def validate_comment_markers(self):
errors = []
document_xml = None
comments_xml = None
for xml_file in self.xml_files:
if xml_file.name == "document.xml" and "word" in str(xml_file):
document_xml = xml_file
elif xml_file.name == "comments.xml":
comments_xml = xml_file
if not document_xml:
if self.verbose:
print("PASSED - No document.xml found (skipping comment validation)")
return True
try:
doc_root = lxml.etree.parse(str(document_xml)).getroot()
namespaces = {"w": self.WORD_2006_NAMESPACE}
range_starts = {
elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id")
for elem in doc_root.xpath(
".//w:commentRangeStart", namespaces=namespaces
)
}
range_ends = {
elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id")
for elem in doc_root.xpath(
".//w:commentRangeEnd", namespaces=namespaces
)
}
references = {
elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id")
for elem in doc_root.xpath(
".//w:commentReference", namespaces=namespaces
)
}
orphaned_ends = range_ends - range_starts
for comment_id in sorted(
orphaned_ends, key=lambda x: int(x) if x and x.isdigit() else 0
):
errors.append(
f' document.xml: commentRangeEnd id="{comment_id}" has no matching commentRangeStart'
)
orphaned_starts = range_starts - range_ends
for comment_id in sorted(
orphaned_starts, key=lambda x: int(x) if x and x.isdigit() else 0
):
errors.append(
f' document.xml: commentRangeStart id="{comment_id}" has no matching commentRangeEnd'
)
comment_ids = set()
if comments_xml and comments_xml.exists():
comments_root = lxml.etree.parse(str(comments_xml)).getroot()
comment_ids = {
elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id")
for elem in comments_root.xpath(
".//w:comment", namespaces=namespaces
)
}
marker_ids = range_starts | range_ends | references
invalid_refs = marker_ids - comment_ids
for comment_id in sorted(
invalid_refs, key=lambda x: int(x) if x and x.isdigit() else 0
):
if comment_id:
errors.append(
f' document.xml: marker id="{comment_id}" references non-existent comment'
)
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(f" Error parsing XML: {e}")
if errors:
print(f"FAILED - {len(errors)} comment marker violations:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - All comment markers properly paired")
return True
def repair(self) -> int:
repairs = super().repair()
repairs += self.repair_durableId()
return repairs
def repair_durableId(self) -> int:
repairs = 0
for xml_file in self.xml_files:
try:
content = xml_file.read_text(encoding="utf-8")
dom = defusedxml.minidom.parseString(content)
modified = False
for elem in dom.getElementsByTagName("*"):
if not elem.hasAttribute("w16cid:durableId"):
continue
durable_id = elem.getAttribute("w16cid:durableId")
needs_repair = False
if xml_file.name == "numbering.xml":
try:
needs_repair = (
self._parse_id_value(durable_id, base=10) >= 0x7FFFFFFF
)
except ValueError:
needs_repair = True
else:
try:
needs_repair = (
self._parse_id_value(durable_id, base=16) >= 0x7FFFFFFF
)
except ValueError:
needs_repair = True
if needs_repair:
value = random.randint(1, 0x7FFFFFFE)
if xml_file.name == "numbering.xml":
new_id = str(value)
else:
new_id = f"{value:08X}"
elem.setAttribute("w16cid:durableId", new_id)
print(
f" Repaired: {xml_file.name}: durableId {durable_id} → {new_id}"
)
repairs += 1
modified = True
if modified:
xml_file.write_bytes(dom.toxml(encoding="UTF-8"))
except Exception:
pass
return repairs
if __name__ == "__main__":
raise RuntimeError("This module should not be run directly.")
@@ -0,0 +1,275 @@
"""
Validator for PowerPoint presentation XML files against XSD schemas.
"""
import re
from .base import BaseSchemaValidator
class PPTXSchemaValidator(BaseSchemaValidator):
PRESENTATIONML_NAMESPACE = (
"http://schemas.openxmlformats.org/presentationml/2006/main"
)
ELEMENT_RELATIONSHIP_TYPES = {
"sldid": "slide",
"sldmasterid": "slidemaster",
"notesmasterid": "notesmaster",
"sldlayoutid": "slidelayout",
"themeid": "theme",
"tablestyleid": "tablestyles",
}
def validate(self):
if not self.validate_xml():
return False
all_valid = True
if not self.validate_namespaces():
all_valid = False
if not self.validate_unique_ids():
all_valid = False
if not self.validate_uuid_ids():
all_valid = False
if not self.validate_file_references():
all_valid = False
if not self.validate_slide_layout_ids():
all_valid = False
if not self.validate_content_types():
all_valid = False
if not self.validate_against_xsd():
all_valid = False
if not self.validate_notes_slide_references():
all_valid = False
if not self.validate_all_relationship_ids():
all_valid = False
if not self.validate_no_duplicate_slide_layouts():
all_valid = False
return all_valid
def validate_uuid_ids(self):
import lxml.etree
errors = []
uuid_pattern = re.compile(
r"^[\{\(]?[0-9A-Fa-f]{8}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{12}[\}\)]?$"
)
for xml_file in self.xml_files:
try:
root = lxml.etree.parse(str(xml_file)).getroot()
for elem in root.iter():
for attr, value in elem.attrib.items():
attr_name = attr.split("}")[-1].lower()
if attr_name == "id" or attr_name.endswith("id"):
if self._looks_like_uuid(value):
if not uuid_pattern.match(value):
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {elem.sourceline}: ID '{value}' appears to be a UUID but contains invalid hex characters"
)
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
)
if errors:
print(f"FAILED - Found {len(errors)} UUID ID validation errors:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - All UUID-like IDs contain valid hex values")
return True
def _looks_like_uuid(self, value):
clean_value = value.strip("{}()").replace("-", "")
return len(clean_value) == 32 and all(c.isalnum() for c in clean_value)
def validate_slide_layout_ids(self):
import lxml.etree
errors = []
slide_masters = list(self.unpacked_dir.glob("ppt/slideMasters/*.xml"))
if not slide_masters:
if self.verbose:
print("PASSED - No slide masters found")
return True
for slide_master in slide_masters:
try:
root = lxml.etree.parse(str(slide_master)).getroot()
rels_file = slide_master.parent / "_rels" / f"{slide_master.name}.rels"
if not rels_file.exists():
errors.append(
f" {slide_master.relative_to(self.unpacked_dir)}: "
f"Missing relationships file: {rels_file.relative_to(self.unpacked_dir)}"
)
continue
rels_root = lxml.etree.parse(str(rels_file)).getroot()
valid_layout_rids = set()
for rel in rels_root.findall(
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
):
rel_type = rel.get("Type", "")
if "slideLayout" in rel_type:
valid_layout_rids.add(rel.get("Id"))
for sld_layout_id in root.findall(
f".//{{{self.PRESENTATIONML_NAMESPACE}}}sldLayoutId"
):
r_id = sld_layout_id.get(
f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id"
)
layout_id = sld_layout_id.get("id")
if r_id and r_id not in valid_layout_rids:
errors.append(
f" {slide_master.relative_to(self.unpacked_dir)}: "
f"Line {sld_layout_id.sourceline}: sldLayoutId with id='{layout_id}' "
f"references r:id='{r_id}' which is not found in slide layout relationships"
)
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(
f" {slide_master.relative_to(self.unpacked_dir)}: Error: {e}"
)
if errors:
print(f"FAILED - Found {len(errors)} slide layout ID validation errors:")
for error in errors:
print(error)
print(
"Remove invalid references or add missing slide layouts to the relationships file."
)
return False
else:
if self.verbose:
print("PASSED - All slide layout IDs reference valid slide layouts")
return True
def validate_no_duplicate_slide_layouts(self):
import lxml.etree
errors = []
slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels"))
for rels_file in slide_rels_files:
try:
root = lxml.etree.parse(str(rels_file)).getroot()
layout_rels = [
rel
for rel in root.findall(
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
)
if "slideLayout" in rel.get("Type", "")
]
if len(layout_rels) > 1:
errors.append(
f" {rels_file.relative_to(self.unpacked_dir)}: has {len(layout_rels)} slideLayout references"
)
except Exception as e:
errors.append(
f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}"
)
if errors:
print("FAILED - Found slides with duplicate slideLayout references:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - All slides have exactly one slideLayout reference")
return True
def validate_notes_slide_references(self):
import lxml.etree
errors = []
notes_slide_references = {}
slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels"))
if not slide_rels_files:
if self.verbose:
print("PASSED - No slide relationship files found")
return True
for rels_file in slide_rels_files:
try:
root = lxml.etree.parse(str(rels_file)).getroot()
for rel in root.findall(
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
):
rel_type = rel.get("Type", "")
if "notesSlide" in rel_type:
target = rel.get("Target", "")
if target:
normalized_target = target.replace("../", "")
slide_name = rels_file.stem.replace(
".xml", ""
)
if normalized_target not in notes_slide_references:
notes_slide_references[normalized_target] = []
notes_slide_references[normalized_target].append(
(slide_name, rels_file)
)
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(
f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}"
)
for target, references in notes_slide_references.items():
if len(references) > 1:
slide_names = [ref[0] for ref in references]
errors.append(
f" Notes slide '{target}' is referenced by multiple slides: {', '.join(slide_names)}"
)
for slide_name, rels_file in references:
errors.append(f" - {rels_file.relative_to(self.unpacked_dir)}")
if errors:
print(
f"FAILED - Found {len([e for e in errors if not e.startswith(' ')])} notes slide reference validation errors:"
)
for error in errors:
print(error)
print("Each slide may optionally have its own slide file.")
return False
else:
if self.verbose:
print("PASSED - All notes slide references are unique")
return True
if __name__ == "__main__":
raise RuntimeError("This module should not be run directly.")
@@ -0,0 +1,247 @@
"""
Validator for tracked changes in Word documents.
"""
import subprocess
import tempfile
import zipfile
from pathlib import Path
class RedliningValidator:
def __init__(self, unpacked_dir, original_docx, verbose=False, author="Claude"):
self.unpacked_dir = Path(unpacked_dir)
self.original_docx = Path(original_docx)
self.verbose = verbose
self.author = author
self.namespaces = {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
}
def repair(self) -> int:
return 0
def validate(self):
modified_file = self.unpacked_dir / "word" / "document.xml"
if not modified_file.exists():
print(f"FAILED - Modified document.xml not found at {modified_file}")
return False
try:
import xml.etree.ElementTree as ET
tree = ET.parse(modified_file)
root = tree.getroot()
del_elements = root.findall(".//w:del", self.namespaces)
ins_elements = root.findall(".//w:ins", self.namespaces)
author_del_elements = [
elem
for elem in del_elements
if elem.get(f"{{{self.namespaces['w']}}}author") == self.author
]
author_ins_elements = [
elem
for elem in ins_elements
if elem.get(f"{{{self.namespaces['w']}}}author") == self.author
]
if not author_del_elements and not author_ins_elements:
if self.verbose:
print(f"PASSED - No tracked changes by {self.author} found.")
return True
except Exception:
pass
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
try:
with zipfile.ZipFile(self.original_docx, "r") as zip_ref:
zip_ref.extractall(temp_path)
except Exception as e:
print(f"FAILED - Error unpacking original docx: {e}")
return False
original_file = temp_path / "word" / "document.xml"
if not original_file.exists():
print(
f"FAILED - Original document.xml not found in {self.original_docx}"
)
return False
try:
import xml.etree.ElementTree as ET
modified_tree = ET.parse(modified_file)
modified_root = modified_tree.getroot()
original_tree = ET.parse(original_file)
original_root = original_tree.getroot()
except ET.ParseError as e:
print(f"FAILED - Error parsing XML files: {e}")
return False
self._remove_author_tracked_changes(original_root)
self._remove_author_tracked_changes(modified_root)
modified_text = self._extract_text_content(modified_root)
original_text = self._extract_text_content(original_root)
if modified_text != original_text:
error_message = self._generate_detailed_diff(
original_text, modified_text
)
print(error_message)
return False
if self.verbose:
print(f"PASSED - All changes by {self.author} are properly tracked")
return True
def _generate_detailed_diff(self, original_text, modified_text):
error_parts = [
f"FAILED - Document text doesn't match after removing {self.author}'s tracked changes",
"",
"Likely causes:",
" 1. Modified text inside another author's <w:ins> or <w:del> tags",
" 2. Made edits without proper tracked changes",
" 3. Didn't nest <w:del> inside <w:ins> when deleting another's insertion",
"",
"For pre-redlined documents, use correct patterns:",
" - To reject another's INSERTION: Nest <w:del> inside their <w:ins>",
" - To restore another's DELETION: Add new <w:ins> AFTER their <w:del>",
"",
]
git_diff = self._get_git_word_diff(original_text, modified_text)
if git_diff:
error_parts.extend(["Differences:", "============", git_diff])
else:
error_parts.append("Unable to generate word diff (git not available)")
return "\n".join(error_parts)
def _get_git_word_diff(self, original_text, modified_text):
try:
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
original_file = temp_path / "original.txt"
modified_file = temp_path / "modified.txt"
original_file.write_text(original_text, encoding="utf-8")
modified_file.write_text(modified_text, encoding="utf-8")
result = subprocess.run(
[
"git",
"diff",
"--word-diff=plain",
"--word-diff-regex=.",
"-U0",
"--no-index",
str(original_file),
str(modified_file),
],
capture_output=True,
text=True,
)
if result.stdout.strip():
lines = result.stdout.split("\n")
content_lines = []
in_content = False
for line in lines:
if line.startswith("@@"):
in_content = True
continue
if in_content and line.strip():
content_lines.append(line)
if content_lines:
return "\n".join(content_lines)
result = subprocess.run(
[
"git",
"diff",
"--word-diff=plain",
"-U0",
"--no-index",
str(original_file),
str(modified_file),
],
capture_output=True,
text=True,
)
if result.stdout.strip():
lines = result.stdout.split("\n")
content_lines = []
in_content = False
for line in lines:
if line.startswith("@@"):
in_content = True
continue
if in_content and line.strip():
content_lines.append(line)
return "\n".join(content_lines)
except (subprocess.CalledProcessError, FileNotFoundError, Exception):
pass
return None
def _remove_author_tracked_changes(self, root):
ins_tag = f"{{{self.namespaces['w']}}}ins"
del_tag = f"{{{self.namespaces['w']}}}del"
author_attr = f"{{{self.namespaces['w']}}}author"
for parent in root.iter():
to_remove = []
for child in parent:
if child.tag == ins_tag and child.get(author_attr) == self.author:
to_remove.append(child)
for elem in to_remove:
parent.remove(elem)
deltext_tag = f"{{{self.namespaces['w']}}}delText"
t_tag = f"{{{self.namespaces['w']}}}t"
for parent in root.iter():
to_process = []
for child in parent:
if child.tag == del_tag and child.get(author_attr) == self.author:
to_process.append((child, list(parent).index(child)))
for del_elem, del_index in reversed(to_process):
for elem in del_elem.iter():
if elem.tag == deltext_tag:
elem.tag = t_tag
for child in reversed(list(del_elem)):
parent.insert(del_index, child)
parent.remove(del_elem)
def _extract_text_content(self, root):
p_tag = f"{{{self.namespaces['w']}}}p"
t_tag = f"{{{self.namespaces['w']}}}t"
paragraphs = []
for p_elem in root.findall(f".//{p_tag}"):
text_parts = []
for t_elem in p_elem.findall(f".//{t_tag}"):
if t_elem.text:
text_parts.append(t_elem.text)
paragraph_text = "".join(text_parts)
if paragraph_text:
paragraphs.append(paragraph_text)
return "\n".join(paragraphs)
if __name__ == "__main__":
raise RuntimeError("This module should not be run directly.")
@@ -0,0 +1,3 @@
<?xml version="1.0" ?>
<w:comments xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14">
</w:comments>
@@ -0,0 +1,3 @@
<?xml version="1.0" ?>
<w15:commentsEx xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14">
</w15:commentsEx>
@@ -0,0 +1,3 @@
<?xml version="1.0" ?>
<w16cex:commentsExtensible xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:cr="http://schemas.microsoft.com/office/comments/2020/reactions" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl cr w16du wp14">
</w16cex:commentsExtensible>
@@ -0,0 +1,3 @@
<?xml version="1.0" ?>
<w16cid:commentsIds xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14">
</w16cid:commentsIds>
@@ -0,0 +1,3 @@
<?xml version="1.0" ?>
<w15:people xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml">
</w15:people>
@@ -0,0 +1,3 @@
obj/
bin/
*.user
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 MiniMaxAI
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,250 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<!-- Document Defaults -->
<w:docDefaults>
<w:rPrDefault>
<w:rPr>
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="SimSun" w:cs="Times New Roman" />
<w:sz w:val="24" />
<w:szCs w:val="24" />
<w:lang w:val="en-US" w:eastAsia="zh-CN" w:bidi="ar-SA" />
</w:rPr>
</w:rPrDefault>
<w:pPrDefault>
<w:pPr>
<w:spacing w:after="0" w:line="480" w:lineRule="auto" />
</w:pPr>
</w:pPrDefault>
</w:docDefaults>
<w:latentStyles w:defLockedState="0" w:defUIPriority="99" w:defSemiHidden="0" w:defUnhideWhenUsed="0" w:defQFormat="0" w:count="376" />
<!-- Normal — Times New Roman 12pt, double spaced, first line indent -->
<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
<w:name w:val="Normal" />
<w:qFormat />
<w:pPr>
<w:spacing w:after="0" w:line="480" w:lineRule="auto" />
<w:ind w:firstLine="720" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" />
<w:sz w:val="24" />
<w:szCs w:val="24" />
</w:rPr>
</w:style>
<!-- Default Paragraph Font -->
<w:style w:type="character" w:default="1" w:styleId="DefaultParagraphFont">
<w:name w:val="Default Paragraph Font" />
<w:uiPriority w:val="1" />
<w:semiHidden />
<w:unhideWhenUsed />
</w:style>
<!-- Heading 1 — Bold, 14pt, no color, no indent -->
<w:style w:type="paragraph" w:styleId="Heading1">
<w:name w:val="heading 1" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="480" w:after="240" w:line="480" w:lineRule="auto" />
<w:ind w:firstLine="0" />
<w:jc w:val="center" />
<w:outlineLvl w:val="0" />
</w:pPr>
<w:rPr>
<w:b />
<w:sz w:val="28" />
<w:szCs w:val="28" />
</w:rPr>
</w:style>
<!-- Heading 2 — Bold, 13pt -->
<w:style w:type="paragraph" w:styleId="Heading2">
<w:name w:val="heading 2" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="360" w:after="120" w:line="480" w:lineRule="auto" />
<w:ind w:firstLine="0" />
<w:outlineLvl w:val="1" />
</w:pPr>
<w:rPr>
<w:b />
<w:sz w:val="26" />
<w:szCs w:val="26" />
</w:rPr>
</w:style>
<!-- Heading 3 — Bold, 12pt -->
<w:style w:type="paragraph" w:styleId="Heading3">
<w:name w:val="heading 3" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="240" w:after="80" w:line="480" w:lineRule="auto" />
<w:ind w:firstLine="0" />
<w:outlineLvl w:val="2" />
</w:pPr>
<w:rPr>
<w:b />
<w:sz w:val="24" />
<w:szCs w:val="24" />
</w:rPr>
</w:style>
<!-- Title — Centered, bold, 14pt (academic title page) -->
<w:style w:type="paragraph" w:styleId="Title">
<w:name w:val="Title" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="10" />
<w:pPr>
<w:spacing w:after="480" w:line="480" w:lineRule="auto" />
<w:ind w:firstLine="0" />
<w:jc w:val="center" />
</w:pPr>
<w:rPr>
<w:b />
<w:sz w:val="28" />
<w:szCs w:val="28" />
</w:rPr>
</w:style>
<!-- Subtitle -->
<w:style w:type="paragraph" w:styleId="Subtitle">
<w:name w:val="Subtitle" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="11" />
<w:pPr>
<w:spacing w:after="240" w:line="480" w:lineRule="auto" />
<w:ind w:firstLine="0" />
<w:jc w:val="center" />
</w:pPr>
<w:rPr>
<w:sz w:val="24" />
<w:szCs w:val="24" />
</w:rPr>
</w:style>
<!-- Quote — Block quote, indented 0.5 inch on both sides -->
<w:style w:type="paragraph" w:styleId="Quote">
<w:name w:val="Quote" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="29" />
<w:pPr>
<w:spacing w:before="240" w:after="240" w:line="480" w:lineRule="auto" />
<w:ind w:left="720" w:right="720" w:firstLine="0" />
</w:pPr>
</w:style>
<!-- Table Normal -->
<w:style w:type="table" w:default="1" w:styleId="TableNormal">
<w:name w:val="Normal Table" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:tblPr>
<w:tblInd w:w="0" w:type="dxa" />
<w:tblCellMar>
<w:top w:w="0" w:type="dxa" />
<w:left w:w="108" w:type="dxa" />
<w:bottom w:w="0" w:type="dxa" />
<w:right w:w="108" w:type="dxa" />
</w:tblCellMar>
</w:tblPr>
</w:style>
<!-- Table Grid — Simple borders, no color -->
<w:style w:type="table" w:styleId="TableGrid">
<w:name w:val="Table Grid" />
<w:basedOn w:val="TableNormal" />
<w:uiPriority w:val="39" />
<w:tblPr>
<w:tblBorders>
<w:top w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:left w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:bottom w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:right w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:insideH w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:insideV w:val="single" w:sz="4" w:space="0" w:color="auto" />
</w:tblBorders>
</w:tblPr>
</w:style>
<!-- Header -->
<w:style w:type="paragraph" w:styleId="Header">
<w:name w:val="header" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:tabs>
<w:tab w:val="center" w:pos="4680" />
<w:tab w:val="right" w:pos="9360" />
</w:tabs>
<w:spacing w:after="0" w:line="240" w:lineRule="auto" />
<w:ind w:firstLine="0" />
</w:pPr>
<w:rPr>
<w:sz w:val="24" />
<w:szCs w:val="24" />
</w:rPr>
</w:style>
<!-- Footer -->
<w:style w:type="paragraph" w:styleId="Footer">
<w:name w:val="footer" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:tabs>
<w:tab w:val="center" w:pos="4680" />
<w:tab w:val="right" w:pos="9360" />
</w:tabs>
<w:spacing w:after="0" w:line="240" w:lineRule="auto" />
<w:ind w:firstLine="0" />
<w:jc w:val="center" />
</w:pPr>
<w:rPr>
<w:sz w:val="24" />
<w:szCs w:val="24" />
</w:rPr>
</w:style>
<!-- Hyperlink -->
<w:style w:type="character" w:styleId="Hyperlink">
<w:name w:val="Hyperlink" />
<w:uiPriority w:val="99" />
<w:unhideWhenUsed />
<w:rPr>
<w:color w:val="0563C1" />
<w:u w:val="single" />
</w:rPr>
</w:style>
</w:styles>
@@ -0,0 +1,284 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<!-- Document Defaults -->
<w:docDefaults>
<w:rPrDefault>
<w:rPr>
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:eastAsia="Microsoft YaHei" w:cs="Arial" />
<w:color w:val="333333" />
<w:sz w:val="22" />
<w:szCs w:val="22" />
<w:lang w:val="en-US" w:eastAsia="zh-CN" w:bidi="ar-SA" />
</w:rPr>
</w:rPrDefault>
<w:pPrDefault>
<w:pPr>
<w:spacing w:after="160" w:line="259" w:lineRule="auto" />
</w:pPr>
</w:pPrDefault>
</w:docDefaults>
<w:latentStyles w:defLockedState="0" w:defUIPriority="99" w:defSemiHidden="0" w:defUnhideWhenUsed="0" w:defQFormat="0" w:count="376" />
<!-- Normal -->
<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
<w:name w:val="Normal" />
<w:qFormat />
<w:pPr>
<w:spacing w:after="160" w:line="240" w:lineRule="auto" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" />
<w:color w:val="333333" />
<w:sz w:val="22" />
<w:szCs w:val="22" />
</w:rPr>
</w:style>
<!-- Default Paragraph Font -->
<w:style w:type="character" w:default="1" w:styleId="DefaultParagraphFont">
<w:name w:val="Default Paragraph Font" />
<w:uiPriority w:val="1" />
<w:semiHidden />
<w:unhideWhenUsed />
</w:style>
<!-- Heading 1 — Dark Blue -->
<w:style w:type="paragraph" w:styleId="Heading1">
<w:name w:val="heading 1" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="480" w:after="240" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="0" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:color w:val="1F3864" />
<w:sz w:val="56" />
<w:szCs w:val="56" />
</w:rPr>
</w:style>
<!-- Heading 2 -->
<w:style w:type="paragraph" w:styleId="Heading2">
<w:name w:val="heading 2" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="360" w:after="120" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="1" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:color w:val="1F3864" />
<w:sz w:val="48" />
<w:szCs w:val="48" />
</w:rPr>
</w:style>
<!-- Heading 3 -->
<w:style w:type="paragraph" w:styleId="Heading3">
<w:name w:val="heading 3" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="240" w:after="80" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="2" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:color w:val="1F3864" />
<w:sz w:val="36" />
<w:szCs w:val="36" />
</w:rPr>
</w:style>
<!-- Heading 4 -->
<w:style w:type="paragraph" w:styleId="Heading4">
<w:name w:val="heading 4" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="160" w:after="80" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="3" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:i />
<w:color w:val="1F3864" />
<w:sz w:val="28" />
<w:szCs w:val="28" />
</w:rPr>
</w:style>
<!-- Title -->
<w:style w:type="paragraph" w:styleId="Title">
<w:name w:val="Title" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="10" />
<w:pPr>
<w:spacing w:after="240" w:line="240" w:lineRule="auto" />
<w:jc w:val="center" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:color w:val="1F3864" />
<w:sz w:val="72" />
<w:szCs w:val="72" />
</w:rPr>
</w:style>
<!-- Subtitle -->
<w:style w:type="paragraph" w:styleId="Subtitle">
<w:name w:val="Subtitle" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="11" />
<w:pPr>
<w:spacing w:after="360" w:line="240" w:lineRule="auto" />
<w:jc w:val="center" />
</w:pPr>
<w:rPr>
<w:i />
<w:color w:val="595959" />
<w:sz w:val="32" />
<w:szCs w:val="32" />
</w:rPr>
</w:style>
<!-- Table Grid — Corporate with blue header -->
<w:style w:type="table" w:default="1" w:styleId="TableNormal">
<w:name w:val="Normal Table" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:tblPr>
<w:tblInd w:w="0" w:type="dxa" />
<w:tblCellMar>
<w:top w:w="0" w:type="dxa" />
<w:left w:w="108" w:type="dxa" />
<w:bottom w:w="0" w:type="dxa" />
<w:right w:w="108" w:type="dxa" />
</w:tblCellMar>
</w:tblPr>
</w:style>
<w:style w:type="table" w:styleId="TableGrid">
<w:name w:val="Table Grid" />
<w:basedOn w:val="TableNormal" />
<w:uiPriority w:val="39" />
<w:tblPr>
<w:tblBorders>
<w:top w:val="single" w:sz="4" w:space="0" w:color="BFBFBF" />
<w:left w:val="single" w:sz="4" w:space="0" w:color="BFBFBF" />
<w:bottom w:val="single" w:sz="4" w:space="0" w:color="BFBFBF" />
<w:right w:val="single" w:sz="4" w:space="0" w:color="BFBFBF" />
<w:insideH w:val="single" w:sz="4" w:space="0" w:color="BFBFBF" />
<w:insideV w:val="single" w:sz="4" w:space="0" w:color="BFBFBF" />
</w:tblBorders>
</w:tblPr>
<w:tblStylePr w:type="firstRow">
<w:rPr>
<w:b />
<w:color w:val="FFFFFF" />
</w:rPr>
<w:tcPr>
<w:shd w:val="clear" w:color="auto" w:fill="2F5496" />
<w:tcBorders>
<w:top w:val="single" w:sz="4" w:space="0" w:color="2F5496" />
<w:left w:val="single" w:sz="4" w:space="0" w:color="2F5496" />
<w:bottom w:val="single" w:sz="4" w:space="0" w:color="2F5496" />
<w:right w:val="single" w:sz="4" w:space="0" w:color="2F5496" />
<w:insideH w:val="single" w:sz="4" w:space="0" w:color="3A6BC5" />
<w:insideV w:val="single" w:sz="4" w:space="0" w:color="3A6BC5" />
</w:tcBorders>
</w:tcPr>
</w:tblStylePr>
<w:tblStylePr w:type="band1Horz">
<w:tcPr>
<w:shd w:val="clear" w:color="auto" w:fill="D9E2F3" />
</w:tcPr>
</w:tblStylePr>
</w:style>
<!-- Header -->
<w:style w:type="paragraph" w:styleId="Header">
<w:name w:val="header" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:tabs>
<w:tab w:val="center" w:pos="4680" />
<w:tab w:val="right" w:pos="9360" />
</w:tabs>
<w:spacing w:after="0" w:line="240" w:lineRule="auto" />
</w:pPr>
<w:rPr>
<w:sz w:val="18" />
<w:szCs w:val="18" />
<w:color w:val="808080" />
</w:rPr>
</w:style>
<!-- Footer -->
<w:style w:type="paragraph" w:styleId="Footer">
<w:name w:val="footer" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:tabs>
<w:tab w:val="center" w:pos="4680" />
<w:tab w:val="right" w:pos="9360" />
</w:tabs>
<w:spacing w:after="0" w:line="240" w:lineRule="auto" />
</w:pPr>
<w:rPr>
<w:sz w:val="18" />
<w:szCs w:val="18" />
<w:color w:val="808080" />
</w:rPr>
</w:style>
<!-- Hyperlink -->
<w:style w:type="character" w:styleId="Hyperlink">
<w:name w:val="Hyperlink" />
<w:uiPriority w:val="99" />
<w:unhideWhenUsed />
<w:rPr>
<w:color w:val="0563C1" />
<w:u w:val="single" />
</w:rPr>
</w:style>
</w:styles>
@@ -0,0 +1,449 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<!-- Document Defaults -->
<w:docDefaults>
<w:rPrDefault>
<w:rPr>
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:eastAsia="SimSun" w:cs="Arial" />
<w:sz w:val="22" />
<w:szCs w:val="22" />
<w:lang w:val="en-US" w:eastAsia="zh-CN" w:bidi="ar-SA" />
</w:rPr>
</w:rPrDefault>
<w:pPrDefault>
<w:pPr>
<w:spacing w:after="160" w:line="259" w:lineRule="auto" />
</w:pPr>
</w:pPrDefault>
</w:docDefaults>
<!-- Latent Styles -->
<w:latentStyles w:defLockedState="0" w:defUIPriority="99" w:defSemiHidden="0" w:defUnhideWhenUsed="0" w:defQFormat="0" w:count="376" />
<!-- Normal (Default Paragraph Style) -->
<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
<w:name w:val="Normal" />
<w:qFormat />
<w:pPr>
<w:spacing w:after="160" w:line="240" w:lineRule="auto" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" />
<w:sz w:val="22" />
<w:szCs w:val="22" />
</w:rPr>
</w:style>
<!-- Default Paragraph Font -->
<w:style w:type="character" w:default="1" w:styleId="DefaultParagraphFont">
<w:name w:val="Default Paragraph Font" />
<w:uiPriority w:val="1" />
<w:semiHidden />
<w:unhideWhenUsed />
</w:style>
<!-- Heading 1 -->
<w:style w:type="paragraph" w:styleId="Heading1">
<w:name w:val="heading 1" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="480" w:after="240" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="0" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:color w:val="2F5496" />
<w:sz w:val="56" />
<w:szCs w:val="56" />
</w:rPr>
</w:style>
<!-- Heading 2 -->
<w:style w:type="paragraph" w:styleId="Heading2">
<w:name w:val="heading 2" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="360" w:after="120" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="1" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:color w:val="2F5496" />
<w:sz w:val="48" />
<w:szCs w:val="48" />
</w:rPr>
</w:style>
<!-- Heading 3 -->
<w:style w:type="paragraph" w:styleId="Heading3">
<w:name w:val="heading 3" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="240" w:after="80" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="2" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:color w:val="2F5496" />
<w:sz w:val="36" />
<w:szCs w:val="36" />
</w:rPr>
</w:style>
<!-- Heading 4 -->
<w:style w:type="paragraph" w:styleId="Heading4">
<w:name w:val="heading 4" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="160" w:after="80" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="3" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:i />
<w:color w:val="2F5496" />
<w:sz w:val="28" />
<w:szCs w:val="28" />
</w:rPr>
</w:style>
<!-- Heading 5 -->
<w:style w:type="paragraph" w:styleId="Heading5">
<w:name w:val="heading 5" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="160" w:after="80" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="4" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:color w:val="2F5496" />
<w:sz w:val="24" />
<w:szCs w:val="24" />
</w:rPr>
</w:style>
<!-- Heading 6 -->
<w:style w:type="paragraph" w:styleId="Heading6">
<w:name w:val="heading 6" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="160" w:after="80" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="5" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:i />
<w:color w:val="2F5496" />
<w:sz w:val="22" />
<w:szCs w:val="22" />
</w:rPr>
</w:style>
<!-- Title -->
<w:style w:type="paragraph" w:styleId="Title">
<w:name w:val="Title" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="10" />
<w:pPr>
<w:spacing w:after="240" w:line="240" w:lineRule="auto" />
<w:jc w:val="center" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:color w:val="2F5496" />
<w:sz w:val="72" />
<w:szCs w:val="72" />
</w:rPr>
</w:style>
<!-- Subtitle -->
<w:style w:type="paragraph" w:styleId="Subtitle">
<w:name w:val="Subtitle" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="11" />
<w:pPr>
<w:spacing w:after="360" w:line="240" w:lineRule="auto" />
<w:jc w:val="center" />
</w:pPr>
<w:rPr>
<w:i />
<w:color w:val="595959" />
<w:sz w:val="32" />
<w:szCs w:val="32" />
</w:rPr>
</w:style>
<!-- Quote -->
<w:style w:type="paragraph" w:styleId="Quote">
<w:name w:val="Quote" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="29" />
<w:pPr>
<w:spacing w:before="240" w:after="240" />
<w:ind w:left="720" w:right="720" />
</w:pPr>
<w:rPr>
<w:i />
<w:color w:val="404040" />
</w:rPr>
</w:style>
<!-- Intense Quote -->
<w:style w:type="paragraph" w:styleId="IntenseQuote">
<w:name w:val="Intense Quote" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="30" />
<w:pPr>
<w:spacing w:before="240" w:after="240" />
<w:ind w:left="720" w:right="720" />
<w:pBdr>
<w:left w:val="single" w:sz="18" w:space="12" w:color="2F5496" />
</w:pBdr>
</w:pPr>
<w:rPr>
<w:b />
<w:i />
<w:color w:val="2F5496" />
</w:rPr>
</w:style>
<!-- TOC Heading -->
<w:style w:type="paragraph" w:styleId="TOCHeading">
<w:name w:val="TOC Heading" />
<w:basedOn w:val="Heading1" />
<w:next w:val="Normal" />
<w:uiPriority w:val="39" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:outlineLvl w:val="9" />
</w:pPr>
</w:style>
<!-- TOC 1 -->
<w:style w:type="paragraph" w:styleId="TOC1">
<w:name w:val="toc 1" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="39" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:spacing w:before="120" w:after="0" />
</w:pPr>
<w:rPr>
<w:b />
</w:rPr>
</w:style>
<!-- TOC 2 -->
<w:style w:type="paragraph" w:styleId="TOC2">
<w:name w:val="toc 2" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="39" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:spacing w:after="0" />
<w:ind w:left="240" />
</w:pPr>
</w:style>
<!-- TOC 3 -->
<w:style w:type="paragraph" w:styleId="TOC3">
<w:name w:val="toc 3" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="39" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:spacing w:after="0" />
<w:ind w:left="480" />
</w:pPr>
</w:style>
<!-- List Bullet -->
<w:style w:type="paragraph" w:styleId="ListBullet">
<w:name w:val="List Bullet" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="36" />
<w:pPr>
<w:spacing w:after="0" />
<w:ind w:left="720" w:hanging="360" />
<w:contextualSpacing />
</w:pPr>
</w:style>
<!-- List Number -->
<w:style w:type="paragraph" w:styleId="ListNumber">
<w:name w:val="List Number" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="36" />
<w:pPr>
<w:spacing w:after="0" />
<w:ind w:left="720" w:hanging="360" />
<w:contextualSpacing />
</w:pPr>
</w:style>
<!-- Table Normal -->
<w:style w:type="table" w:default="1" w:styleId="TableNormal">
<w:name w:val="Normal Table" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:tblPr>
<w:tblInd w:w="0" w:type="dxa" />
<w:tblCellMar>
<w:top w:w="0" w:type="dxa" />
<w:left w:w="108" w:type="dxa" />
<w:bottom w:w="0" w:type="dxa" />
<w:right w:w="108" w:type="dxa" />
</w:tblCellMar>
</w:tblPr>
</w:style>
<!-- Table Grid -->
<w:style w:type="table" w:styleId="TableGrid">
<w:name w:val="Table Grid" />
<w:basedOn w:val="TableNormal" />
<w:uiPriority w:val="39" />
<w:tblPr>
<w:tblBorders>
<w:top w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:left w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:bottom w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:right w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:insideH w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:insideV w:val="single" w:sz="4" w:space="0" w:color="auto" />
</w:tblBorders>
</w:tblPr>
</w:style>
<!-- Header -->
<w:style w:type="paragraph" w:styleId="Header">
<w:name w:val="header" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:tabs>
<w:tab w:val="center" w:pos="4680" />
<w:tab w:val="right" w:pos="9360" />
</w:tabs>
<w:spacing w:after="0" w:line="240" w:lineRule="auto" />
</w:pPr>
<w:rPr>
<w:sz w:val="18" />
<w:szCs w:val="18" />
<w:color w:val="808080" />
</w:rPr>
</w:style>
<!-- Footer -->
<w:style w:type="paragraph" w:styleId="Footer">
<w:name w:val="footer" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:tabs>
<w:tab w:val="center" w:pos="4680" />
<w:tab w:val="right" w:pos="9360" />
</w:tabs>
<w:spacing w:after="0" w:line="240" w:lineRule="auto" />
</w:pPr>
<w:rPr>
<w:sz w:val="18" />
<w:szCs w:val="18" />
<w:color w:val="808080" />
</w:rPr>
</w:style>
<!-- Hyperlink -->
<w:style w:type="character" w:styleId="Hyperlink">
<w:name w:val="Hyperlink" />
<w:uiPriority w:val="99" />
<w:unhideWhenUsed />
<w:rPr>
<w:color w:val="0563C1" />
<w:u w:val="single" />
</w:rPr>
</w:style>
<!-- Comment Text -->
<w:style w:type="paragraph" w:styleId="CommentText">
<w:name w:val="annotation text" />
<w:basedOn w:val="Normal" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:spacing w:line="240" w:lineRule="auto" />
</w:pPr>
<w:rPr>
<w:sz w:val="20" />
<w:szCs w:val="20" />
</w:rPr>
</w:style>
<!-- Comment Reference -->
<w:style w:type="character" w:styleId="CommentReference">
<w:name w:val="annotation reference" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:rPr>
<w:sz w:val="16" />
<w:szCs w:val="16" />
</w:rPr>
</w:style>
</w:styles>
@@ -0,0 +1,470 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- ============================================================================ -->
<!-- Aesthetic Rules Schema for minimax-docx -->
<!-- ============================================================================ -->
<!-- Purpose: Validates whether a document follows basic aesthetic rules that -->
<!-- produce visually harmonious results. This is a "taste checker" that flags -->
<!-- common ugly patterns. -->
<!-- -->
<!-- IMPORTANT: XSD validates STRUCTURE and VALUE RANGES, not SEMANTICS. -->
<!-- Many aesthetic rules require cross-element comparison (e.g., "H1 must be -->
<!-- larger than H2") which XSD cannot express. These rules are documented in -->
<!-- comments and must be enforced by a programmatic validator. -->
<!-- -->
<!-- Rules that CAN be expressed in XSD: -->
<!-- - Font size ranges (body 10-14pt, headings 10-26pt) -->
<!-- - Line spacing ranges (1.0x to 2.33x) -->
<!-- - Margin minimums (at least 0.5in on all sides) -->
<!-- - Table cell padding minimums -->
<!-- -->
<!-- Rules that CANNOT be expressed in XSD (enforce programmatically): -->
<!-- - H1 sz > H2 sz > H3 sz > body sz (hierarchy) -->
<!-- - Maximum 3 font families across all styles -->
<!-- - Heading space-before >= space-after -->
<!-- - Color contrast ratio between text and background -->
<!-- - Consistent font family within heading vs body groups -->
<!-- - Line spacing and font size harmony (larger text needs tighter spacing) -->
<!-- -->
<!-- MIT License - minimax-docx project -->
<!-- ============================================================================ -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
targetNamespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
elementFormDefault="qualified">
<!-- ============================================================ -->
<!-- RULE 1: Body Font Size Range -->
<!-- ============================================================ -->
<!-- Body text must be 10-14pt (half-points: 20-28). -->
<!-- WHY: Below 10pt is hard to read for most adults. -->
<!-- Above 14pt body text looks childish or wasteful. -->
<!-- The sweet spot is 10.5-12pt for most font families. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticBodyFontSize">
<xs:annotation>
<xs:documentation>
Body text font size in half-points.
Acceptable range: 20-28 (10pt-14pt).
- 10pt (20): minimum for comfortable reading
- 11pt (22): modern default (Calibri, Aptos)
- 12pt (24): traditional default (Times New Roman)
- 14pt (28): maximum before body text looks oversized
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="20"/> <!-- 10pt minimum -->
<xs:maxInclusive value="28"/> <!-- 14pt maximum -->
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 2: Heading Font Size Range -->
<!-- ============================================================ -->
<!-- Headings must be 12-26pt (half-points: 24-52). -->
<!-- WHY: Below 12pt, a heading cannot be visually distinguished -->
<!-- from body text by size alone. -->
<!-- Above 26pt is poster-sized and wastes vertical space. -->
<!-- NOTE: Some academic styles use 12pt headings (same as body) -->
<!-- and differentiate via bold/italic/centering instead. -->
<!-- The lower bound of 24 (12pt) accommodates this. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticHeadingFontSize">
<xs:annotation>
<xs:documentation>
Heading font size in half-points.
Acceptable range: 24-52 (12pt-26pt).
- 12pt (24): APA-style (hierarchy via bold/italic, not size)
- 16pt (32): typical H2/H3
- 20pt (40): typical H1
- 26pt (52): maximum before headings dominate the page
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="24"/> <!-- 12pt minimum -->
<xs:maxInclusive value="52"/> <!-- 26pt maximum -->
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 3: Line Spacing Range -->
<!-- ============================================================ -->
<!-- Line spacing (in auto mode) must be 240-560 (1.0x-2.33x). -->
<!-- WHY: Below 1.0x, ascenders/descenders overlap — unreadable. -->
<!-- Above 2.33x, lines appear disconnected. -->
<!-- Sweet spots: 1.15x (276) for sans, 1.5x (360) for -->
<!-- generous layouts, 2.0x (480) for academic. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticLineSpacing">
<xs:annotation>
<xs:documentation>
Line spacing value for auto line-spacing rule.
In 240ths of single spacing: 240 = 1.0x, 480 = 2.0x.
Acceptable range: 240-560 (1.0x to 2.33x).
Common values:
- 240: single spacing (dense, technical)
- 259: Word's 1.08x default
- 276: 1.15x (modern corporate default)
- 336: 1.4x (executive/generous)
- 360: 1.5x (generous/minimal)
- 480: 2.0x (academic double spacing)
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="240"/> <!-- 1.0x single spacing -->
<xs:maxInclusive value="560"/> <!-- ~2.33x — beyond double feels disconnected -->
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 3b: Fixed Line Spacing Range -->
<!-- ============================================================ -->
<!-- For lineRule="exact", line value is in DXA (twentieths of pt) -->
<!-- Range: 200-720 DXA (10pt-36pt fixed line height) -->
<!-- Chinese government standard uses 560 DXA (28pt). -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticFixedLineSpacing">
<xs:annotation>
<xs:documentation>
Fixed line spacing value (lineRule="exact") in DXA.
Acceptable range: 200-720 (10pt-36pt).
- 560: Chinese government standard (28pt, for 16pt body)
- 480: double-space equivalent for 12pt body
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="200"/> <!-- 10pt minimum fixed height -->
<xs:maxInclusive value="720"/> <!-- 36pt maximum fixed height -->
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 4: Margin Minimums -->
<!-- ============================================================ -->
<!-- All margins must be at least 720 DXA (0.5 inch). -->
<!-- WHY: Below 0.5in, most printers clip content. -->
<!-- Also, narrow margins create a cramped, unprofessional -->
<!-- appearance. Even "full bleed" designs need internal -->
<!-- text margins. -->
<!-- Max set to 4320 DXA (3 inches) to prevent absurd margins. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticMargin">
<xs:annotation>
<xs:documentation>
Page margin in DXA. Minimum 720 (0.5 inch), maximum 4320 (3 inches).
Common values:
- 720: 0.5in (minimum printable)
- 1440: 1.0in (standard US)
- 1588: 28mm (Chinese government left margin)
- 1800: 1.25in (executive/premium)
- 2160: 1.5in (binding margin or narrow-column design)
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="720"/> <!-- 0.5in — minimum for print safety -->
<xs:maxInclusive value="4320"/> <!-- 3in — beyond this is absurd -->
</xs:restriction>
</xs:simpleType>
<!-- Top/bottom margins: signed because negative values can create -->
<!-- overlap effects, but we still enforce a reasonable minimum. -->
<xs:simpleType name="ST_AestheticVerticalMargin">
<xs:annotation>
<xs:documentation>
Vertical (top/bottom) page margin in DXA.
Range: 360 to 4320 (0.25in to 3in).
Slightly more permissive than horizontal margins because
header/footer areas may reduce effective vertical margin.
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:integer">
<xs:minInclusive value="360"/> <!-- 0.25in — tighter vertical is sometimes acceptable -->
<xs:maxInclusive value="4320"/>
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 5: Paragraph Spacing Ranges -->
<!-- ============================================================ -->
<!-- Space before/after paragraphs should be 0-960 DXA (0-48pt). -->
<!-- WHY: More than 48pt of space before/after creates awkward -->
<!-- gaps that disrupt reading flow. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticParaSpacing">
<xs:annotation>
<xs:documentation>
Paragraph spacing (before/after) in DXA.
Range: 0-960 (0pt-48pt).
Common values:
- 0: academic style (uses first-line indent instead)
- 80: 4pt (tight, used after H2/H3)
- 120: 6pt (moderate)
- 160: 8pt (standard modern spacing)
- 200: 10pt (generous/executive)
- 240: 12pt (very generous/minimal)
- 480: 24pt (heading before — creates section break)
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:nonNegativeInteger">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="960"/> <!-- 48pt max — beyond this is a page break, not spacing -->
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 6: Table Cell Padding Minimum -->
<!-- ============================================================ -->
<!-- Table cells need at least 28 DXA (~1.4pt) padding. -->
<!-- WHY: Without padding, text touches cell borders — visually -->
<!-- cramped and hard to read. Even borderless tables need -->
<!-- padding for column separation. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticCellPadding">
<xs:annotation>
<xs:documentation>
Table cell padding in DXA. Minimum 28 DXA (~1.4pt).
Recommended: 57 DXA (~2.85pt) for comfortable spacing.
Maximum: 288 DXA (~14pt) — beyond this wastes space.
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:nonNegativeInteger">
<xs:minInclusive value="28"/> <!-- ~1.4pt minimum breathing room -->
<xs:maxInclusive value="288"/> <!-- ~14pt — more than this is excessive -->
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 7: Border Size Range -->
<!-- ============================================================ -->
<!-- Border size (in eighth-points) should be 2-24 (0.25pt-3pt). -->
<!-- WHY: Below 0.25pt borders may not render or print. -->
<!-- Above 3pt borders look heavy and distracting. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticBorderSize">
<xs:annotation>
<xs:documentation>
Border width in eighth-points.
Range: 2-24 (0.25pt to 3pt).
Common values:
- 4: 0.5pt (thin, standard)
- 6: 0.75pt (header separator in three-line tables)
- 8: 1.0pt (medium, good for framing borders)
- 12: 1.5pt (heavy, used for top/bottom in three-line tables)
- 24: 3.0pt (maximum before borders dominate)
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="2"/> <!-- 0.25pt minimum visible -->
<xs:maxInclusive value="24"/> <!-- 3pt maximum tasteful -->
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 8: Color Value Format -->
<!-- ============================================================ -->
<!-- Colors must be valid 6-digit hex (RRGGBB) or "auto". -->
<!-- This is structural validation, not aesthetic validation. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticColor">
<xs:annotation>
<xs:documentation>
Color value: 6-digit hex (RRGGBB) or "auto".
Examples: "000000", "1F3864", "2C3E50", "auto".
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string">
<xs:pattern value="[0-9A-Fa-f]{6}|auto"/>
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 9: First-Line Indent Range -->
<!-- ============================================================ -->
<!-- If first-line indent is used, it should be 360-1440 DXA -->
<!-- (0.25in - 1.0in). -->
<!-- WHY: Below 0.25in the indent is barely visible. -->
<!-- Above 1.0in the indent looks like a tab error. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticFirstLineIndent">
<xs:annotation>
<xs:documentation>
First-line indent in DXA. Range: 0-1440 (0in to 1.0in).
- 0: no indent (modern style with space-after)
- 480: 0.33in (compact)
- 640: ~0.44in (2 Chinese characters at 16pt)
- 720: 0.5in (standard APA/academic)
- 1440: 1.0in (maximum before it looks wrong)
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:nonNegativeInteger">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="1440"/>
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- COMPOSITE TYPE: Aesthetic Run Properties Check -->
<!-- ============================================================ -->
<!-- Validates run-level properties for aesthetic compliance. -->
<!-- ============================================================ -->
<xs:complexType name="CT_AestheticRPr">
<xs:annotation>
<xs:documentation>
Aesthetic run properties validator.
Checks font size and color format at the run level.
</xs:documentation>
</xs:annotation>
<xs:all>
<xs:element name="sz" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="w:ST_AestheticBodyFontSize" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="szCs" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="w:ST_AestheticBodyFontSize" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="color" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="w:ST_AestheticColor" use="required"/>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
<!-- ============================================================ -->
<!-- COMPOSITE TYPE: Aesthetic Spacing Check -->
<!-- ============================================================ -->
<xs:complexType name="CT_AestheticSpacing">
<xs:annotation>
<xs:documentation>
Aesthetic spacing validator for paragraph spacing properties.
Validates line spacing and before/after spacing are in range.
</xs:documentation>
</xs:annotation>
<xs:attribute name="line" type="w:ST_AestheticLineSpacing" use="optional"/>
<xs:attribute name="before" type="w:ST_AestheticParaSpacing" use="optional"/>
<xs:attribute name="after" type="w:ST_AestheticParaSpacing" use="optional"/>
<xs:attribute name="lineRule" use="optional">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="auto"/>
<xs:enumeration value="exact"/>
<xs:enumeration value="atLeast"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<!-- ============================================================ -->
<!-- COMPOSITE TYPE: Aesthetic Page Margins Check -->
<!-- ============================================================ -->
<xs:complexType name="CT_AestheticPageMargins">
<xs:annotation>
<xs:documentation>
Aesthetic page margin validator.
Ensures all margins meet minimum print-safe thresholds.
</xs:documentation>
</xs:annotation>
<xs:attribute name="top" type="w:ST_AestheticVerticalMargin" use="required"/>
<xs:attribute name="bottom" type="w:ST_AestheticVerticalMargin" use="required"/>
<xs:attribute name="left" type="w:ST_AestheticMargin" use="required"/>
<xs:attribute name="right" type="w:ST_AestheticMargin" use="required"/>
<xs:attribute name="header" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="footer" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="gutter" type="xs:nonNegativeInteger" use="optional"/>
</xs:complexType>
<!-- ============================================================ -->
<!-- COMPOSITE TYPE: Aesthetic Table Cell Margin Check -->
<!-- ============================================================ -->
<xs:complexType name="CT_AestheticTableCellMargin">
<xs:annotation>
<xs:documentation>
Aesthetic table cell margin validator.
Ensures minimum padding for readability.
</xs:documentation>
</xs:annotation>
<xs:attribute name="w" type="w:ST_AestheticCellPadding" use="required"/>
<xs:attribute name="type" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="dxa"/>
<xs:enumeration value="nil"/>
<xs:enumeration value="pct"/>
<xs:enumeration value="auto"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<!-- ============================================================ -->
<!-- PROGRAMMATIC RULES (cannot be expressed in XSD) -->
<!-- ============================================================ -->
<!-- -->
<!-- The following rules must be checked by a programmatic -->
<!-- validator (e.g., AestheticRuleValidator.cs). They are -->
<!-- documented here for completeness. -->
<!-- -->
<!-- ── RULE P1: Heading Size Hierarchy ── -->
<!-- H1 sz >= H2 sz >= H3 sz >= body sz -->
<!-- Exception: APA-style where all headings = body size. -->
<!-- Implementation: Collect sz from Heading1/2/3 styles and -->
<!-- docDefaults. Verify monotonic decrease (or equality). -->
<!-- -->
<!-- ── RULE P2: Maximum 3 Font Families ── -->
<!-- Across docDefaults rPr + all style rPr, at most 3 distinct -->
<!-- font families (by Ascii name) should be used. -->
<!-- WHY: More than 3 fonts creates visual chaos. Professional -->
<!-- designs typically use 1-2 families. -->
<!-- Implementation: Collect all rFonts.ascii values from -->
<!-- docDefaults and all styles. Count distinct. Warn if > 3. -->
<!-- -->
<!-- ── RULE P3: Heading Space-Before >= Space-After ── -->
<!-- For heading styles, spaceBefore should be >= spaceAfter. -->
<!-- WHY: Headings should be visually closer to the content they -->
<!-- introduce than to the content above. This is the -->
<!-- "proximity principle" of Gestalt design. -->
<!-- Implementation: For each Heading style, compare pPr spacing -->
<!-- before vs after values. -->
<!-- -->
<!-- ── RULE P4: Spacing-Size Coherence ── -->
<!-- Paragraph after-spacing should be proportional to body size: -->
<!-- after >= bodySize * 0.5 AND after <= bodySize * 1.5 -->
<!-- WHY: Too little spacing makes paragraphs run together. -->
<!-- Too much spacing disconnects them. -->
<!-- Implementation: Get body sz from docDefaults, convert to DXA -->
<!-- (multiply by 10), check after-spacing ratio. -->
<!-- -->
<!-- ── RULE P5: Color Consistency ── -->
<!-- All heading styles should use the same color value. -->
<!-- Body text color (if set) should be consistent across styles. -->
<!-- WHY: Inconsistent colors look accidental, not designed. -->
<!-- Exception: Caption and footnote styles may differ. -->
<!-- Implementation: Collect color.val from heading styles. -->
<!-- Verify all are identical. -->
<!-- -->
<!-- ── RULE P6: Indent/Spacing Mutual Exclusion ── -->
<!-- If first-line indent > 0 in docDefaults, then after-spacing -->
<!-- should be 0 (and vice versa). Using BOTH indent AND spacing -->
<!-- is visually redundant — it signals uncertainty. -->
<!-- Exception: Headings may override this. -->
<!-- Implementation: Check docDefaults pPr. If firstLine > 0 AND -->
<!-- after > 0, emit a warning (not error). -->
<!-- -->
<!-- ── RULE P7: Table Border Consistency ── -->
<!-- Within a single table, border styles should be internally -->
<!-- consistent (all single, or all none — not a random mix). -->
<!-- Implementation: Check tblBorders for consistent val values. -->
<!-- -->
<!-- ── RULE P8: Line Spacing vs Font Size Harmony ── -->
<!-- For fixed line spacing (lineRule="exact"): -->
<!-- lineHeight >= fontSize * 1.2 -->
<!-- WHY: Fixed line spacing less than 1.2x the font size causes -->
<!-- ascender/descender clipping. -->
<!-- Implementation: When lineRule="exact", compare line value -->
<!-- against the effective font size. -->
<!-- -->
<!-- ============================================================ -->
</xs:schema>
@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Business Rules Gate-Check Schema for minimax-docx -->
<!-- Used in Scenario C (template application) as hard gate -->
<!-- Validates business compliance beyond XML correctness -->
<!-- MIT License - minimax-docx project -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
targetNamespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
elementFormDefault="qualified">
<!-- ============================================================ -->
<!-- Page margins: constrained to reasonable bounds -->
<!-- Minimum 360 DXA (0.25 inch), maximum 4320 DXA (3 inches) -->
<!-- ============================================================ -->
<xs:simpleType name="ST_MarginMeasure">
<xs:restriction base="xs:integer">
<xs:minInclusive value="360"/>
<xs:maxInclusive value="4320"/>
</xs:restriction>
</xs:simpleType>
<!-- Signed margin (top/bottom can be negative for overlap) -->
<xs:simpleType name="ST_SignedMarginMeasure">
<xs:restriction base="xs:integer">
<xs:minInclusive value="-4320"/>
<xs:maxInclusive value="4320"/>
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- Font size constraints -->
<!-- Body text: 16-144 half-points (8-72pt) -->
<!-- Heading text: 20-192 half-points (10-96pt) -->
<!-- ============================================================ -->
<xs:simpleType name="ST_BodyFontSize">
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="16"/>
<xs:maxInclusive value="144"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="ST_HeadingFontSize">
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="20"/>
<xs:maxInclusive value="192"/>
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- Valid standard page sizes (width x height in DXA) -->
<!-- ============================================================ -->
<!-- Letter: 12240 x 15840 -->
<!-- A4: 11906 x 16838 -->
<!-- Legal: 12240 x 20160 -->
<!-- A3: 16838 x 23811 -->
<!-- A5: 8391 x 11906 -->
<xs:simpleType name="ST_PageWidth">
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="5040"/>
<xs:maxInclusive value="31680"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="ST_PageHeight">
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="5040"/>
<xs:maxInclusive value="31680"/>
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- Constrained section properties for gate-check -->
<!-- ============================================================ -->
<xs:complexType name="CT_GateCheckSectPr">
<xs:all>
<xs:element name="pgSz" minOccurs="1">
<xs:complexType>
<xs:attribute name="w" type="w:ST_PageWidth" use="required"/>
<xs:attribute name="h" type="w:ST_PageHeight" use="required"/>
<xs:attribute name="orient" use="optional">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="portrait"/>
<xs:enumeration value="landscape"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="pgMar" minOccurs="1">
<xs:complexType>
<xs:attribute name="top" type="w:ST_SignedMarginMeasure" use="required"/>
<xs:attribute name="bottom" type="w:ST_SignedMarginMeasure" use="required"/>
<xs:attribute name="left" type="w:ST_MarginMeasure" use="required"/>
<xs:attribute name="right" type="w:ST_MarginMeasure" use="required"/>
<xs:attribute name="header" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="footer" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="gutter" type="xs:nonNegativeInteger" use="optional"/>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
<!-- ============================================================ -->
<!-- Required styles: at minimum Normal and Heading1 must exist -->
<!-- This is enforced programmatically by GateCheckValidator -->
<!-- rather than via XSD, since XSD cannot validate style presence -->
<!-- across separate XML parts. -->
<!-- ============================================================ -->
<!-- ============================================================ -->
<!-- Constrained run properties for font size validation -->
<!-- ============================================================ -->
<xs:complexType name="CT_GateCheckRPr">
<xs:all>
<xs:element name="sz" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="w:ST_BodyFontSize" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="szCs" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="w:ST_BodyFontSize" use="required"/>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,159 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Common type definitions for WordprocessingML subset schema -->
<!-- MIT License - minimax-docx project -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
elementFormDefault="qualified">
<!-- Measurement: non-negative twips (1/1440 inch) -->
<xs:simpleType name="ST_TwipsMeasure">
<xs:restriction base="xs:nonNegativeInteger"/>
</xs:simpleType>
<!-- Measurement: signed twips (for negative margins/indents) -->
<xs:simpleType name="ST_SignedTwipsMeasure">
<xs:restriction base="xs:integer"/>
</xs:simpleType>
<!-- Half-point measure for font sizes (1 = 0.5pt) -->
<xs:simpleType name="ST_HpsMeasure">
<xs:restriction base="xs:positiveInteger"/>
</xs:simpleType>
<!-- Hex color: 6 hex digits -->
<xs:simpleType name="ST_HexColor">
<xs:restriction base="xs:string">
<xs:pattern value="auto|[0-9a-fA-F]{6}"/>
</xs:restriction>
</xs:simpleType>
<!-- On/Off toggle -->
<xs:simpleType name="ST_OnOff">
<xs:restriction base="xs:string">
<xs:enumeration value="true"/>
<xs:enumeration value="false"/>
<xs:enumeration value="0"/>
<xs:enumeration value="1"/>
</xs:restriction>
</xs:simpleType>
<!-- Justification -->
<xs:simpleType name="ST_Jc">
<xs:restriction base="xs:string">
<xs:enumeration value="left"/>
<xs:enumeration value="center"/>
<xs:enumeration value="right"/>
<xs:enumeration value="both"/>
<xs:enumeration value="distribute"/>
<xs:enumeration value="start"/>
<xs:enumeration value="end"/>
</xs:restriction>
</xs:simpleType>
<!-- Break type -->
<xs:simpleType name="ST_BrType">
<xs:restriction base="xs:string">
<xs:enumeration value="page"/>
<xs:enumeration value="column"/>
<xs:enumeration value="textWrapping"/>
</xs:restriction>
</xs:simpleType>
<!-- Underline patterns -->
<xs:simpleType name="ST_Underline">
<xs:restriction base="xs:string">
<xs:enumeration value="none"/>
<xs:enumeration value="single"/>
<xs:enumeration value="words"/>
<xs:enumeration value="double"/>
<xs:enumeration value="thick"/>
<xs:enumeration value="dotted"/>
<xs:enumeration value="dash"/>
<xs:enumeration value="dotDash"/>
<xs:enumeration value="dotDotDash"/>
<xs:enumeration value="wave"/>
</xs:restriction>
</xs:simpleType>
<!-- Vertical alignment for subscript/superscript -->
<xs:simpleType name="ST_VerticalAlignRun">
<xs:restriction base="xs:string">
<xs:enumeration value="baseline"/>
<xs:enumeration value="superscript"/>
<xs:enumeration value="subscript"/>
</xs:restriction>
</xs:simpleType>
<!-- Section break type -->
<xs:simpleType name="ST_SectionMark">
<xs:restriction base="xs:string">
<xs:enumeration value="nextPage"/>
<xs:enumeration value="nextColumn"/>
<xs:enumeration value="continuous"/>
<xs:enumeration value="evenPage"/>
<xs:enumeration value="oddPage"/>
</xs:restriction>
</xs:simpleType>
<!-- Header/footer type -->
<xs:simpleType name="ST_HdrFtr">
<xs:restriction base="xs:string">
<xs:enumeration value="even"/>
<xs:enumeration value="default"/>
<xs:enumeration value="first"/>
</xs:restriction>
</xs:simpleType>
<!-- Table width type -->
<xs:simpleType name="ST_TblWidth">
<xs:restriction base="xs:string">
<xs:enumeration value="auto"/>
<xs:enumeration value="dxa"/>
<xs:enumeration value="nil"/>
<xs:enumeration value="pct"/>
</xs:restriction>
</xs:simpleType>
<!-- Vertical merge -->
<xs:simpleType name="ST_Merge">
<xs:restriction base="xs:string">
<xs:enumeration value="continue"/>
<xs:enumeration value="restart"/>
</xs:restriction>
</xs:simpleType>
<!-- Highlight colors -->
<xs:simpleType name="ST_HighlightColor">
<xs:restriction base="xs:string">
<xs:enumeration value="black"/>
<xs:enumeration value="blue"/>
<xs:enumeration value="cyan"/>
<xs:enumeration value="darkBlue"/>
<xs:enumeration value="darkCyan"/>
<xs:enumeration value="darkGray"/>
<xs:enumeration value="darkGreen"/>
<xs:enumeration value="darkMagenta"/>
<xs:enumeration value="darkRed"/>
<xs:enumeration value="darkYellow"/>
<xs:enumeration value="green"/>
<xs:enumeration value="lightGray"/>
<xs:enumeration value="magenta"/>
<xs:enumeration value="none"/>
<xs:enumeration value="red"/>
<xs:enumeration value="white"/>
<xs:enumeration value="yellow"/>
</xs:restriction>
</xs:simpleType>
<!-- Percentage (for table width pct, etc.) -->
<xs:simpleType name="ST_DecimalNumber">
<xs:restriction base="xs:integer"/>
</xs:simpleType>
<!-- Relationship ID reference -->
<xs:simpleType name="ST_RelationshipId">
<xs:restriction base="xs:string"/>
</xs:simpleType>
</xs:schema>
@@ -0,0 +1,589 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- WordprocessingML Subset Schema for minimax-docx -->
<!-- Curated subset of ISO 29500 covering elements agents commonly generate -->
<!-- MIT License - minimax-docx project -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
targetNamespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
elementFormDefault="qualified">
<xs:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"/>
<xs:import namespace="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"/>
<!-- ============================================================ -->
<!-- Root element -->
<!-- ============================================================ -->
<xs:element name="document" type="w:CT_Document"/>
<xs:complexType name="CT_Document">
<xs:sequence>
<xs:element name="body" type="w:CT_Body" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<!-- ============================================================ -->
<!-- Body -->
<!-- ============================================================ -->
<xs:complexType name="CT_Body">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="p" type="w:CT_P"/>
<xs:element name="tbl" type="w:CT_Tbl"/>
<xs:element name="sdt" type="w:CT_SdtBlock"/>
<xs:element name="bookmarkStart" type="w:CT_BookmarkStart"/>
<xs:element name="bookmarkEnd" type="w:CT_BookmarkEnd"/>
</xs:choice>
<xs:element name="sectPr" type="w:CT_SectPr" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<!-- ============================================================ -->
<!-- Paragraph -->
<!-- ============================================================ -->
<xs:complexType name="CT_P">
<xs:sequence>
<xs:element name="pPr" type="w:CT_PPr" minOccurs="0"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="r" type="w:CT_R"/>
<xs:element name="hyperlink" type="w:CT_Hyperlink"/>
<xs:element name="bookmarkStart" type="w:CT_BookmarkStart"/>
<xs:element name="bookmarkEnd" type="w:CT_BookmarkEnd"/>
<xs:element name="commentRangeStart" type="w:CT_MarkupRange"/>
<xs:element name="commentRangeEnd" type="w:CT_MarkupRange"/>
<xs:element name="ins" type="w:CT_RunTrackChange"/>
<xs:element name="del" type="w:CT_RunTrackChange"/>
</xs:choice>
</xs:sequence>
<xs:attribute ref="r:id" use="optional"/>
</xs:complexType>
<!-- Paragraph Properties -->
<xs:complexType name="CT_PPr">
<xs:all>
<xs:element name="pStyle" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="keepNext" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="keepLines" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="pageBreakBefore" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="widowControl" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="numPr" type="w:CT_NumPr" minOccurs="0"/>
<xs:element name="spacing" type="w:CT_Spacing" minOccurs="0"/>
<xs:element name="ind" type="w:CT_Ind" minOccurs="0"/>
<xs:element name="jc" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="outlineLvl" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:integer" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="rPr" type="w:CT_RPr" minOccurs="0"/>
<xs:element name="pBdr" type="w:CT_PBdr" minOccurs="0"/>
<xs:element name="shd" type="w:CT_Shd" minOccurs="0"/>
<xs:element name="tabs" type="w:CT_Tabs" minOccurs="0"/>
<xs:element name="sectPr" type="w:CT_SectPr" minOccurs="0"/>
</xs:all>
</xs:complexType>
<!-- ============================================================ -->
<!-- Run -->
<!-- ============================================================ -->
<xs:complexType name="CT_R">
<xs:sequence>
<xs:element name="rPr" type="w:CT_RPr" minOccurs="0"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="t" type="w:CT_Text"/>
<xs:element name="delText" type="w:CT_Text"/>
<xs:element name="br" type="w:CT_Br"/>
<xs:element name="tab" type="w:CT_Empty"/>
<xs:element name="cr" type="w:CT_Empty"/>
<xs:element name="drawing" type="w:CT_Drawing"/>
<xs:element name="commentReference" type="w:CT_MarkupRef"/>
<xs:element name="footnoteReference" type="w:CT_FtnEdnRef"/>
<xs:element name="endnoteReference" type="w:CT_FtnEdnRef"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
<!-- Run Properties -->
<xs:complexType name="CT_RPr">
<xs:all>
<xs:element name="rStyle" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="rFonts" type="w:CT_Fonts" minOccurs="0"/>
<xs:element name="b" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="bCs" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="i" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="iCs" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="caps" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="smallCaps" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="strike" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="dstrike" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="vanish" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="color" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
<xs:attribute name="themeColor" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="spacing" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:integer" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="sz" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:positiveInteger" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="szCs" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:positiveInteger" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="highlight" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="u" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
<xs:attribute name="color" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="vertAlign" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="lang" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="optional"/>
<xs:attribute name="eastAsia" type="xs:string" use="optional"/>
<xs:attribute name="bidi" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
<!-- ============================================================ -->
<!-- Text -->
<!-- ============================================================ -->
<xs:complexType name="CT_Text" mixed="true">
<xs:attribute ref="xml:space" use="optional"/>
</xs:complexType>
<!-- ============================================================ -->
<!-- Table -->
<!-- ============================================================ -->
<xs:complexType name="CT_Tbl">
<xs:sequence>
<xs:element name="tblPr" type="w:CT_TblPr" minOccurs="0"/>
<xs:element name="tblGrid" type="w:CT_TblGrid" minOccurs="0"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="tr" type="w:CT_Row"/>
<xs:element name="bookmarkStart" type="w:CT_BookmarkStart"/>
<xs:element name="bookmarkEnd" type="w:CT_BookmarkEnd"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
<xs:complexType name="CT_TblPr">
<xs:all>
<xs:element name="tblStyle" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="tblW" type="w:CT_TblWidth" minOccurs="0"/>
<xs:element name="jc" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="tblInd" type="w:CT_TblWidth" minOccurs="0"/>
<xs:element name="tblBorders" type="w:CT_TblBorders" minOccurs="0"/>
<xs:element name="shd" type="w:CT_Shd" minOccurs="0"/>
<xs:element name="tblLayout" minOccurs="0">
<xs:complexType>
<xs:attribute name="type" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="tblCellMar" type="w:CT_TblCellMar" minOccurs="0"/>
<xs:element name="tblLook" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="optional"/>
<xs:attribute name="firstRow" type="xs:string" use="optional"/>
<xs:attribute name="lastRow" type="xs:string" use="optional"/>
<xs:attribute name="firstColumn" type="xs:string" use="optional"/>
<xs:attribute name="lastColumn" type="xs:string" use="optional"/>
<xs:attribute name="noHBand" type="xs:string" use="optional"/>
<xs:attribute name="noVBand" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_TblGrid">
<xs:sequence>
<xs:element name="gridCol" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="w" type="xs:nonNegativeInteger" use="optional"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="CT_Row">
<xs:sequence>
<xs:element name="trPr" type="w:CT_TrPr" minOccurs="0"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="tc" type="w:CT_Cell"/>
<xs:element name="bookmarkStart" type="w:CT_BookmarkStart"/>
<xs:element name="bookmarkEnd" type="w:CT_BookmarkEnd"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
<xs:complexType name="CT_TrPr">
<xs:all>
<xs:element name="trHeight" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="hRule" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="tblHeader" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="jc" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_Cell">
<xs:sequence>
<xs:element name="tcPr" type="w:CT_TcPr" minOccurs="0"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="p" type="w:CT_P"/>
<xs:element name="tbl" type="w:CT_Tbl"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
<xs:complexType name="CT_TcPr">
<xs:all>
<xs:element name="tcW" type="w:CT_TblWidth" minOccurs="0"/>
<xs:element name="gridSpan" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:positiveInteger" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="vMerge" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="tcBorders" type="w:CT_TcBorders" minOccurs="0"/>
<xs:element name="shd" type="w:CT_Shd" minOccurs="0"/>
<xs:element name="vAlign" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="noWrap" type="w:CT_OnOff" minOccurs="0"/>
</xs:all>
</xs:complexType>
<!-- ============================================================ -->
<!-- Section Properties -->
<!-- ============================================================ -->
<xs:complexType name="CT_SectPr">
<xs:all>
<xs:element name="headerReference" minOccurs="0">
<xs:complexType>
<xs:attribute name="type" type="xs:string" use="required"/>
<xs:attribute ref="r:id" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="footerReference" minOccurs="0">
<xs:complexType>
<xs:attribute name="type" type="xs:string" use="required"/>
<xs:attribute ref="r:id" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="type" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="pgSz" minOccurs="0">
<xs:complexType>
<xs:attribute name="w" type="xs:nonNegativeInteger" use="required"/>
<xs:attribute name="h" type="xs:nonNegativeInteger" use="required"/>
<xs:attribute name="orient" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="pgMar" minOccurs="0">
<xs:complexType>
<xs:attribute name="top" type="xs:integer" use="required"/>
<xs:attribute name="right" type="xs:nonNegativeInteger" use="required"/>
<xs:attribute name="bottom" type="xs:integer" use="required"/>
<xs:attribute name="left" type="xs:nonNegativeInteger" use="required"/>
<xs:attribute name="header" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="footer" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="gutter" type="xs:nonNegativeInteger" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="pgNumType" minOccurs="0">
<xs:complexType>
<xs:attribute name="fmt" type="xs:string" use="optional"/>
<xs:attribute name="start" type="xs:nonNegativeInteger" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="cols" minOccurs="0">
<xs:complexType>
<xs:attribute name="space" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="num" type="xs:positiveInteger" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="docGrid" minOccurs="0">
<xs:complexType>
<xs:attribute name="linePitch" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="type" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="titlePg" type="w:CT_OnOff" minOccurs="0"/>
</xs:all>
</xs:complexType>
<!-- ============================================================ -->
<!-- Hyperlink -->
<!-- ============================================================ -->
<xs:complexType name="CT_Hyperlink">
<xs:sequence>
<xs:element name="r" type="w:CT_R" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute ref="r:id" use="optional"/>
<xs:attribute name="anchor" type="xs:string" use="optional"/>
<xs:attribute name="history" type="xs:string" use="optional"/>
</xs:complexType>
<!-- ============================================================ -->
<!-- Track Changes -->
<!-- ============================================================ -->
<xs:complexType name="CT_RunTrackChange">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="r" type="w:CT_R"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="id" type="xs:nonNegativeInteger" use="required"/>
<xs:attribute name="author" type="xs:string" use="required"/>
<xs:attribute name="date" type="xs:dateTime" use="optional"/>
</xs:complexType>
<!-- ============================================================ -->
<!-- Bookmarks -->
<!-- ============================================================ -->
<xs:complexType name="CT_BookmarkStart">
<xs:attribute name="id" type="xs:nonNegativeInteger" use="required"/>
<xs:attribute name="name" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="CT_BookmarkEnd">
<xs:attribute name="id" type="xs:nonNegativeInteger" use="required"/>
</xs:complexType>
<!-- ============================================================ -->
<!-- Comments -->
<!-- ============================================================ -->
<xs:complexType name="CT_MarkupRange">
<xs:attribute name="id" type="xs:nonNegativeInteger" use="required"/>
</xs:complexType>
<xs:complexType name="CT_MarkupRef">
<xs:attribute name="id" type="xs:nonNegativeInteger" use="required"/>
</xs:complexType>
<!-- ============================================================ -->
<!-- Footnote/Endnote reference -->
<!-- ============================================================ -->
<xs:complexType name="CT_FtnEdnRef">
<xs:attribute name="id" type="xs:nonNegativeInteger" use="required"/>
</xs:complexType>
<!-- ============================================================ -->
<!-- Drawing (basic inline image) -->
<!-- ============================================================ -->
<xs:complexType name="CT_Drawing">
<xs:sequence>
<xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<!-- ============================================================ -->
<!-- Structured Document Tag (content control) -->
<!-- ============================================================ -->
<xs:complexType name="CT_SdtBlock">
<xs:sequence>
<xs:element name="sdtPr" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="sdtContent" minOccurs="0">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="p" type="w:CT_P"/>
<xs:element name="tbl" type="w:CT_Tbl"/>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<!-- ============================================================ -->
<!-- Helper types -->
<!-- ============================================================ -->
<xs:complexType name="CT_OnOff">
<xs:attribute name="val" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_Empty"/>
<xs:complexType name="CT_Br">
<xs:attribute name="type" type="xs:string" use="optional"/>
<xs:attribute name="clear" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_Fonts">
<xs:attribute name="ascii" type="xs:string" use="optional"/>
<xs:attribute name="hAnsi" type="xs:string" use="optional"/>
<xs:attribute name="eastAsia" type="xs:string" use="optional"/>
<xs:attribute name="cs" type="xs:string" use="optional"/>
<xs:attribute name="asciiTheme" type="xs:string" use="optional"/>
<xs:attribute name="hAnsiTheme" type="xs:string" use="optional"/>
<xs:attribute name="eastAsiaTheme" type="xs:string" use="optional"/>
<xs:attribute name="cstheme" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_NumPr">
<xs:all>
<xs:element name="ilvl" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:nonNegativeInteger" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="numId" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:nonNegativeInteger" use="required"/>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_Spacing">
<xs:attribute name="before" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="after" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="line" type="xs:integer" use="optional"/>
<xs:attribute name="lineRule" type="xs:string" use="optional"/>
<xs:attribute name="beforeAutospacing" type="xs:string" use="optional"/>
<xs:attribute name="afterAutospacing" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_Ind">
<xs:attribute name="left" type="xs:integer" use="optional"/>
<xs:attribute name="right" type="xs:integer" use="optional"/>
<xs:attribute name="hanging" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="firstLine" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="start" type="xs:integer" use="optional"/>
<xs:attribute name="end" type="xs:integer" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_TblWidth">
<xs:attribute name="w" type="xs:string" use="optional"/>
<xs:attribute name="type" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_Shd">
<xs:attribute name="val" type="xs:string" use="optional"/>
<xs:attribute name="color" type="xs:string" use="optional"/>
<xs:attribute name="fill" type="xs:string" use="optional"/>
<xs:attribute name="themeFill" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_Border">
<xs:attribute name="val" type="xs:string" use="required"/>
<xs:attribute name="sz" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="space" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="color" type="xs:string" use="optional"/>
<xs:attribute name="themeColor" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_PBdr">
<xs:all>
<xs:element name="top" type="w:CT_Border" minOccurs="0"/>
<xs:element name="left" type="w:CT_Border" minOccurs="0"/>
<xs:element name="bottom" type="w:CT_Border" minOccurs="0"/>
<xs:element name="right" type="w:CT_Border" minOccurs="0"/>
<xs:element name="between" type="w:CT_Border" minOccurs="0"/>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_TblBorders">
<xs:all>
<xs:element name="top" type="w:CT_Border" minOccurs="0"/>
<xs:element name="left" type="w:CT_Border" minOccurs="0"/>
<xs:element name="bottom" type="w:CT_Border" minOccurs="0"/>
<xs:element name="right" type="w:CT_Border" minOccurs="0"/>
<xs:element name="insideH" type="w:CT_Border" minOccurs="0"/>
<xs:element name="insideV" type="w:CT_Border" minOccurs="0"/>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_TcBorders">
<xs:all>
<xs:element name="top" type="w:CT_Border" minOccurs="0"/>
<xs:element name="left" type="w:CT_Border" minOccurs="0"/>
<xs:element name="bottom" type="w:CT_Border" minOccurs="0"/>
<xs:element name="right" type="w:CT_Border" minOccurs="0"/>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_TblCellMar">
<xs:all>
<xs:element name="top" type="w:CT_TblWidth" minOccurs="0"/>
<xs:element name="left" type="w:CT_TblWidth" minOccurs="0"/>
<xs:element name="bottom" type="w:CT_TblWidth" minOccurs="0"/>
<xs:element name="right" type="w:CT_TblWidth" minOccurs="0"/>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_Tabs">
<xs:sequence>
<xs:element name="tab" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
<xs:attribute name="pos" type="xs:integer" use="required"/>
<xs:attribute name="leader" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
echo "Usage: $(basename "$0") <file.doc> [output_directory]"
echo "Convert .doc to .docx using LibreOffice."
exit 1
}
if [ $# -lt 1 ]; then
usage
fi
INPUT="$1"
OUTDIR="${2:-.}"
if [ ! -f "$INPUT" ]; then
echo "Error: File not found: $INPUT"
exit 1
fi
if ! command -v soffice &>/dev/null; then
echo "Error: soffice (LibreOffice) is required for .doc conversion but not found."
echo "Install LibreOffice: brew install --cask libreoffice"
exit 1
fi
BASENAME=$(basename "$INPUT" .doc)
mkdir -p "$OUTDIR"
echo "Converting: $INPUT -> $OUTDIR/$BASENAME.docx"
soffice --headless --convert-to docx --outdir "$OUTDIR" "$INPUT" >/dev/null 2>&1
OUTPUT="$OUTDIR/$BASENAME.docx"
if [ ! -f "$OUTPUT" ]; then
echo "Error: Conversion failed. Output file not created: $OUTPUT"
exit 1
fi
echo "Success: $OUTPUT"
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
echo "Usage: $(basename "$0") <file.docx>"
echo "Preview DOCX content as plain text."
exit 1
}
if [ $# -lt 1 ]; then
usage
fi
INPUT="$1"
if [ ! -f "$INPUT" ]; then
echo "Error: File not found: $INPUT"
exit 1
fi
FILE_SIZE=$(du -h "$INPUT" | cut -f1)
echo "=== DOCX Preview: $(basename "$INPUT") ==="
echo "File size: $FILE_SIZE"
if command -v pandoc &>/dev/null; then
CONTENT=$(pandoc -f docx -t plain "$INPUT" 2>/dev/null)
WORD_COUNT=$(echo "$CONTENT" | wc -w | tr -d ' ')
EST_PAGES=$(( (WORD_COUNT + 249) / 250 ))
echo "Word count: $WORD_COUNT"
echo "Estimated pages: $EST_PAGES"
echo "---"
echo "$CONTENT"
else
echo "(pandoc not available, falling back to raw XML extract)"
echo "---"
unzip -p "$INPUT" word/document.xml 2>/dev/null | head -100
fi
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\MiniMaxAIDocx.Core\MiniMaxAIDocx.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.5" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NeutralLanguage>en</NeutralLanguage>
</PropertyGroup>
</Project>
@@ -0,0 +1,18 @@
using System.CommandLine;
using MiniMaxAIDocx.Core.Commands;
var rootCommand = new RootCommand("minimax-docx: OpenXML document generation and manipulation CLI");
// Scenario commands
rootCommand.Add(CreateCommand.Create());
rootCommand.Add(EditContentCommand.Create());
rootCommand.Add(ApplyTemplateCommand.Create());
// Tool commands
rootCommand.Add(ValidateCommand.Create());
rootCommand.Add(MergeRunsCommand.Create());
rootCommand.Add(FixOrderCommand.Create());
rootCommand.Add(AnalyzeCommand.Create());
rootCommand.Add(DiffCommand.Create());
return rootCommand.Parse(args).Invoke();
@@ -0,0 +1,147 @@
using System.CommandLine;
using System.IO.Compression;
using System.Text.Json;
using System.Xml.Linq;
namespace MiniMaxAIDocx.Core.Commands;
public static class AnalyzeCommand
{
private static readonly XNamespace W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
private static readonly XNamespace WP = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
public static Command Create()
{
var inputOption = new Option<string>("--input") { Description = "DOCX file to analyze", Required = true };
var jsonOption = new Option<bool>("--json") { Description = "Output as JSON" };
var cmd = new Command("analyze", "Analyze document structure and styles")
{
inputOption, jsonOption
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOption)!;
var asJson = parseResult.GetValue(jsonOption);
if (!File.Exists(input))
{
Console.Error.WriteLine($"File not found: {input}");
return;
}
using var zip = ZipFile.OpenRead(input);
var docEntry = zip.GetEntry("word/document.xml");
if (docEntry == null)
{
Console.Error.WriteLine("Not a valid DOCX");
return;
}
XDocument doc;
using (var stream = docEntry.Open())
doc = XDocument.Load(stream);
var body = doc.Root?.Element(W + "body");
if (body == null) return;
// Sections
var sections = body.Descendants(W + "sectPr").ToList();
var sectionBreaks = sections.Select(s => (string?)s.Element(W + "type")?.Attribute(W + "val") ?? "nextPage").ToList();
// Headings
var headings = new List<object>();
foreach (var p in body.Descendants(W + "p"))
{
var style = (string?)p.Element(W + "pPr")?.Element(W + "pStyle")?.Attribute(W + "val");
if (style?.StartsWith("Heading", StringComparison.OrdinalIgnoreCase) == true)
{
var text = string.Concat(p.Descendants(W + "t").Select(t => t.Value));
headings.Add(new { style, text });
}
}
// Tables
var tables = body.Descendants(W + "tbl").Select(tbl => new
{
rows = tbl.Elements(W + "tr").Count(),
cols = tbl.Elements(W + "tr").FirstOrDefault()?.Elements(W + "tc").Count() ?? 0
}).ToList();
// Images
var images = body.Descendants(W + "drawing").Count();
// Headers/footers
var headerRefs = sections.SelectMany(s => s.Elements(W + "headerReference")).Count();
var footerRefs = sections.SelectMany(s => s.Elements(W + "footerReference")).Count();
// Paragraphs and word count
var paragraphs = body.Descendants(W + "p").ToList();
var allText = string.Concat(body.Descendants(W + "t").Select(t => t.Value));
var wordCount = allText.Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries).Length;
// XML file sizes
var fileSizes = zip.Entries
.Where(e => e.FullName.StartsWith("word/") && e.FullName.EndsWith(".xml"))
.Select(e => new { file = e.FullName, size = e.Length })
.OrderByDescending(e => e.size)
.ToList();
// Styles
var styleNames = new List<string>();
var stylesEntry = zip.GetEntry("word/styles.xml");
if (stylesEntry != null)
{
using var stream = stylesEntry.Open();
var stylesDoc = XDocument.Load(stream);
styleNames = stylesDoc.Descendants(W + "style")
.Where(s => (string?)s.Attribute(W + "customStyle") == "1")
.Select(s => (string?)s.Attribute(W + "styleId") ?? "")
.Where(s => s != "")
.ToList();
}
var analysis = new
{
sections = new { count = sections.Count, breakTypes = sectionBreaks },
headings,
tables = new { count = tables.Count, details = tables },
images,
headerFooter = new { headers = headerRefs, footers = footerRefs },
paragraphs = paragraphs.Count,
estimatedWordCount = wordCount,
xmlFileSizes = fileSizes,
customStyles = new { count = styleNames.Count, names = styleNames }
};
if (asJson)
{
Console.WriteLine(JsonSerializer.Serialize(analysis, new JsonSerializerOptions { WriteIndented = true }));
}
else
{
Console.WriteLine($"Sections: {sections.Count} ({string.Join(", ", sectionBreaks)})");
Console.WriteLine($"Headings: {headings.Count}");
foreach (var h in headings)
Console.WriteLine($" {h}");
Console.WriteLine($"Tables: {tables.Count}");
foreach (var t in tables)
Console.WriteLine($" {t.rows} rows x {t.cols} cols");
Console.WriteLine($"Images: {images}");
Console.WriteLine($"Headers: {headerRefs}");
Console.WriteLine($"Footers: {footerRefs}");
Console.WriteLine($"Paragraphs: {paragraphs.Count}");
Console.WriteLine($"Word count: ~{wordCount}");
Console.WriteLine($"Custom styles: {styleNames.Count}");
foreach (var s in styleNames)
Console.WriteLine($" {s}");
Console.WriteLine("XML file sizes:");
foreach (var f in fileSizes)
Console.WriteLine($" {f.file}: {f.size:N0} bytes");
}
});
return cmd;
}
}
@@ -0,0 +1,322 @@
using System.CommandLine;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
namespace MiniMaxAIDocx.Core.Commands;
/// <summary>
/// Scenario C: Apply formatting from a template DOCX to a source DOCX.
/// Copies styles, theme, numbering, headers/footers, and section properties
/// from the template while preserving all content from the source.
/// </summary>
public static class ApplyTemplateCommand
{
public static Command Create()
{
var inputOpt = new Option<string>("--input") { Description = "Source DOCX (content to keep)", Required = true };
var templateOpt = new Option<string>("--template") { Description = "Template DOCX (formatting to apply)", Required = true };
var outputOpt = new Option<string>("--output") { Description = "Output DOCX file path", Required = true };
var applyStylesOpt = new Option<bool>("--apply-styles") { Description = "Copy styles.xml from template" };
applyStylesOpt.DefaultValueFactory = _ => true;
var applyThemeOpt = new Option<bool>("--apply-theme") { Description = "Copy theme from template" };
applyThemeOpt.DefaultValueFactory = _ => true;
var applyNumberingOpt = new Option<bool>("--apply-numbering") { Description = "Copy numbering.xml from template" };
applyNumberingOpt.DefaultValueFactory = _ => true;
var applyHeadersFootersOpt = new Option<bool>("--apply-headers-footers") { Description = "Copy headers/footers from template" };
var applySectionsOpt = new Option<bool>("--apply-sections") { Description = "Apply section properties from template" };
applySectionsOpt.DefaultValueFactory = _ => true;
var cmd = new Command("apply-template", "Apply template formatting to a DOCX")
{
inputOpt, templateOpt, outputOpt, applyStylesOpt, applyThemeOpt,
applyNumberingOpt, applyHeadersFootersOpt, applySectionsOpt
};
cmd.SetAction((parseResult) =>
{
var inputPath = parseResult.GetValue(inputOpt)!;
var templatePath = parseResult.GetValue(templateOpt)!;
var outputPath = parseResult.GetValue(outputOpt)!;
var applyStyles = parseResult.GetValue(applyStylesOpt);
var applyTheme = parseResult.GetValue(applyThemeOpt);
var applyNumbering = parseResult.GetValue(applyNumberingOpt);
var applyHeadersFooters = parseResult.GetValue(applyHeadersFootersOpt);
var applySections = parseResult.GetValue(applySectionsOpt);
if (!File.Exists(inputPath)) { Console.Error.WriteLine($"Input file not found: {inputPath}"); return; }
if (!File.Exists(templatePath)) { Console.Error.WriteLine($"Template file not found: {templatePath}"); return; }
// Create output as a copy of the source
File.Copy(inputPath, outputPath, overwrite: true);
using var output = WordprocessingDocument.Open(outputPath, true);
using var template = WordprocessingDocument.Open(templatePath, false);
var outputMain = output.MainDocumentPart;
var templateMain = template.MainDocumentPart;
if (outputMain == null || templateMain == null)
{
Console.Error.WriteLine("Invalid document: missing main document part.");
return;
}
int appliedCount = 0;
if (applyStyles)
{
CopyStyles(templateMain, outputMain);
appliedCount++;
Console.WriteLine(" Applied: styles");
}
if (applyTheme)
{
CopyTheme(templateMain, outputMain);
appliedCount++;
Console.WriteLine(" Applied: theme");
}
if (applyNumbering)
{
CopyNumbering(templateMain, outputMain);
appliedCount++;
Console.WriteLine(" Applied: numbering");
}
if (applyHeadersFooters)
{
CopyHeadersAndFooters(templateMain, outputMain);
appliedCount++;
Console.WriteLine(" Applied: headers/footers");
}
if (applySections)
{
CopySectionProperties(templateMain, outputMain);
appliedCount++;
Console.WriteLine(" Applied: section properties");
}
outputMain.Document.Save();
Console.WriteLine($"Applied {appliedCount} formatting component(s) from template to {outputPath}");
});
return cmd;
}
/// <summary>
/// Replaces the output's StyleDefinitionsPart with the template's version.
/// </summary>
private static void CopyStyles(MainDocumentPart template, MainDocumentPart output)
{
var templateStyles = template.StyleDefinitionsPart;
if (templateStyles == null) return;
if (output.StyleDefinitionsPart != null)
output.DeletePart(output.StyleDefinitionsPart);
var newStylesPart = output.AddNewPart<StyleDefinitionsPart>();
using var stream = templateStyles.GetStream(FileMode.Open, FileAccess.Read);
newStylesPart.FeedData(stream);
}
/// <summary>
/// Replaces the output's ThemePart with the template's version.
/// </summary>
private static void CopyTheme(MainDocumentPart template, MainDocumentPart output)
{
var templateTheme = template.ThemePart;
if (templateTheme == null) return;
if (output.ThemePart != null)
output.DeletePart(output.ThemePart);
var newThemePart = output.AddNewPart<ThemePart>();
using var stream = templateTheme.GetStream(FileMode.Open, FileAccess.Read);
newThemePart.FeedData(stream);
}
/// <summary>
/// Copies numbering definitions from template, remapping numbering IDs
/// referenced in the output document's paragraphs.
/// </summary>
private static void CopyNumbering(MainDocumentPart template, MainDocumentPart output)
{
var templateNumbering = template.NumberingDefinitionsPart;
if (templateNumbering == null) return;
var referencedNumIds = new HashSet<string>();
var body = output.Document.Body;
if (body != null)
{
foreach (var numId in body.Descendants<NumberingId>())
{
if (numId.Val?.Value != null)
referencedNumIds.Add(numId.Val.Value.ToString());
}
}
if (output.NumberingDefinitionsPart != null)
output.DeletePart(output.NumberingDefinitionsPart);
var newNumberingPart = output.AddNewPart<NumberingDefinitionsPart>();
using var stream = templateNumbering.GetStream(FileMode.Open, FileAccess.Read);
newNumberingPart.FeedData(stream);
if (referencedNumIds.Count > 0)
{
Console.WriteLine($" Note: {referencedNumIds.Count} numbering reference(s) in document content mapped to template definitions.");
}
}
/// <summary>
/// Copies headers and footers from the template, remapping relationship IDs.
/// </summary>
private static void CopyHeadersAndFooters(MainDocumentPart template, MainDocumentPart output)
{
var outputBody = output.Document.Body;
if (outputBody == null) return;
// Remove existing header/footer parts from output
foreach (var hp in output.HeaderParts.ToList())
output.DeletePart(hp);
foreach (var fp in output.FooterParts.ToList())
output.DeletePart(fp);
// Remove existing header/footer references from all section properties
foreach (var sectPr in outputBody.Descendants<SectionProperties>())
{
foreach (var hr in sectPr.Elements<HeaderReference>().ToList())
hr.Remove();
foreach (var fr in sectPr.Elements<FooterReference>().ToList())
fr.Remove();
}
var templateBody = template.Document?.Body;
if (templateBody == null) return;
var templateFinalSectPr = templateBody.Descendants<SectionProperties>().LastOrDefault();
if (templateFinalSectPr == null) return;
var outputFinalSectPr = outputBody.Descendants<SectionProperties>().LastOrDefault();
if (outputFinalSectPr == null)
{
outputFinalSectPr = new SectionProperties();
outputBody.Append(outputFinalSectPr);
}
// Copy headers
foreach (var headerRef in templateFinalSectPr.Elements<HeaderReference>())
{
var templateHeaderPart = template.GetPartById(headerRef.Id!) as HeaderPart;
if (templateHeaderPart == null) continue;
var newHeaderPart = output.AddNewPart<HeaderPart>();
using (var stream = templateHeaderPart.GetStream(FileMode.Open, FileAccess.Read))
{
newHeaderPart.FeedData(stream);
}
CopyPartRelationships(templateHeaderPart, newHeaderPart);
var newRefId = output.GetIdOfPart(newHeaderPart);
outputFinalSectPr.InsertAt(new HeaderReference
{
Type = headerRef.Type,
Id = newRefId
}, 0);
}
// Copy footers
foreach (var footerRef in templateFinalSectPr.Elements<FooterReference>())
{
var templateFooterPart = template.GetPartById(footerRef.Id!) as FooterPart;
if (templateFooterPart == null) continue;
var newFooterPart = output.AddNewPart<FooterPart>();
using (var stream = templateFooterPart.GetStream(FileMode.Open, FileAccess.Read))
{
newFooterPart.FeedData(stream);
}
CopyPartRelationships(templateFooterPart, newFooterPart);
var newRefId = output.GetIdOfPart(newFooterPart);
var lastHeaderRef = outputFinalSectPr.Elements<HeaderReference>().LastOrDefault();
if (lastHeaderRef != null)
lastHeaderRef.InsertAfterSelf(new FooterReference { Type = footerRef.Type, Id = newRefId });
else
outputFinalSectPr.InsertAt(new FooterReference { Type = footerRef.Type, Id = newRefId }, 0);
}
}
/// <summary>
/// Copies sub-relationships (images, etc.) from a source part to a target part.
/// </summary>
private static void CopyPartRelationships(OpenXmlPart source, OpenXmlPart target)
{
foreach (var rel in source.ExternalRelationships)
{
target.AddExternalRelationship(rel.RelationshipType, rel.Uri, rel.Id);
}
foreach (var childPart in source.Parts)
{
try
{
var contentType = childPart.OpenXmlPart.ContentType;
if (contentType.StartsWith("image/"))
{
var newChild = target.AddNewPart<ImagePart>(contentType, childPart.RelationshipId);
using var stream = childPart.OpenXmlPart.GetStream(FileMode.Open, FileAccess.Read);
newChild.FeedData(stream);
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"[WARN] Skipped non-image embedded part: {ex.Message}");
}
}
}
/// <summary>
/// Copies page size, margins, columns, and document grid from template section properties.
/// </summary>
private static void CopySectionProperties(MainDocumentPart template, MainDocumentPart output)
{
var templateBody = template.Document?.Body;
var outputBody = output.Document?.Body;
if (templateBody == null || outputBody == null) return;
var templateSectPr = templateBody.Descendants<SectionProperties>().LastOrDefault();
if (templateSectPr == null) return;
var outputSectPr = outputBody.Descendants<SectionProperties>().LastOrDefault();
if (outputSectPr == null)
{
outputSectPr = new SectionProperties();
outputBody.Append(outputSectPr);
}
CopyChildElement<PageSize>(templateSectPr, outputSectPr);
CopyChildElement<PageMargin>(templateSectPr, outputSectPr);
CopyChildElement<Columns>(templateSectPr, outputSectPr);
CopyChildElement<DocGrid>(templateSectPr, outputSectPr);
CopyChildElement<PageBorders>(templateSectPr, outputSectPr);
}
private static void CopyChildElement<T>(SectionProperties source, SectionProperties target) where T : OpenXmlElement
{
var sourceElement = source.GetFirstChild<T>();
if (sourceElement == null) return;
var existing = target.GetFirstChild<T>();
existing?.Remove();
target.Append((T)sourceElement.CloneNode(true));
}
}
@@ -0,0 +1,324 @@
using System.CommandLine;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using MiniMaxAIDocx.Core.OpenXml;
using MiniMaxAIDocx.Core.Typography;
namespace MiniMaxAIDocx.Core.Commands;
/// <summary>
/// Scenario A: Create a new DOCX document from scratch with proper styles, sections,
/// headers/footers, and typography defaults.
/// </summary>
public static class CreateCommand
{
public static Command Create()
{
var outputOption = new Option<string>("--output") { Description = "Output DOCX file path", Required = true };
var typeOption = new Option<string>("--type") { Description = "Document type: report, letter, memo, academic" };
typeOption.DefaultValueFactory = _ => "report";
var titleOption = new Option<string>("--title") { Description = "Document title" };
var authorOption = new Option<string>("--author") { Description = "Document author" };
var pageSizeOption = new Option<string>("--page-size") { Description = "Page size: letter, a4, legal, a3" };
pageSizeOption.DefaultValueFactory = _ => "letter";
var marginsOption = new Option<string>("--margins") { Description = "Margin preset: standard, narrow, wide" };
marginsOption.DefaultValueFactory = _ => "standard";
var headerTextOption = new Option<string>("--header") { Description = "Header text" };
var footerTextOption = new Option<string>("--footer") { Description = "Footer text" };
var pageNumbersOption = new Option<bool>("--page-numbers") { Description = "Add page numbers in footer" };
var tocOption = new Option<bool>("--toc") { Description = "Insert table of contents placeholder" };
var contentJsonOption = new Option<string>("--content-json") { Description = "Path to JSON file describing document content" };
var cmd = new Command("create", "Create a new DOCX document from scratch")
{
outputOption, typeOption, titleOption, authorOption, pageSizeOption,
marginsOption, headerTextOption, footerTextOption, pageNumbersOption,
tocOption, contentJsonOption
};
cmd.SetAction((parseResult) =>
{
var output = parseResult.GetValue(outputOption)!;
var docType = parseResult.GetValue(typeOption) ?? "report";
var title = parseResult.GetValue(titleOption);
var author = parseResult.GetValue(authorOption);
var pageSizeName = parseResult.GetValue(pageSizeOption) ?? "letter";
var marginsName = parseResult.GetValue(marginsOption) ?? "standard";
var headerText = parseResult.GetValue(headerTextOption);
var footerText = parseResult.GetValue(footerTextOption);
var pageNumbers = parseResult.GetValue(pageNumbersOption);
var tocPlaceholder = parseResult.GetValue(tocOption);
var contentJson = parseResult.GetValue(contentJsonOption);
var fontConfig = GetFontConfig(docType);
var pageSize = GetPageSizeConfig(pageSizeName);
var margins = GetMargins(marginsName);
using var doc = WordprocessingDocument.Create(output, WordprocessingDocumentType.Document);
var mainPart = doc.AddMainDocumentPart();
mainPart.Document = new Document(new Body());
var body = mainPart.Document.Body!;
// Add styles part with defaults
AddDefaultStyles(mainPart, fontConfig);
// Add section properties (page size, margins)
var sectPr = new SectionProperties();
sectPr.Append(new DocumentFormat.OpenXml.Wordprocessing.PageSize
{
Width = (UInt32Value)(uint)pageSize.WidthDxa,
Height = (UInt32Value)(uint)pageSize.HeightDxa
});
sectPr.Append(new PageMargin
{
Top = margins.TopDxa,
Bottom = margins.BottomDxa,
Left = (UInt32Value)(uint)margins.LeftDxa,
Right = (UInt32Value)(uint)margins.RightDxa
});
// Add header if requested
if (!string.IsNullOrEmpty(headerText))
{
var headerPart = mainPart.AddNewPart<HeaderPart>();
headerPart.Header = new Header(
new Paragraph(new Run(new Text(headerText))));
var headerRefId = mainPart.GetIdOfPart(headerPart);
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = headerRefId
});
}
// Add footer if requested
if (!string.IsNullOrEmpty(footerText) || pageNumbers)
{
var footerPart = mainPart.AddNewPart<FooterPart>();
var footerParagraph = new Paragraph();
if (!string.IsNullOrEmpty(footerText))
{
footerParagraph.Append(new Run(new Text(footerText)));
}
if (pageNumbers)
{
if (!string.IsNullOrEmpty(footerText))
footerParagraph.Append(new Run(new Text(" — ") { Space = SpaceProcessingModeValues.Preserve }));
footerParagraph.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.Begin }));
footerParagraph.Append(new Run(
new FieldCode(" PAGE ") { Space = SpaceProcessingModeValues.Preserve }));
footerParagraph.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.End }));
}
footerPart.Footer = new Footer(footerParagraph);
var footerRefId = mainPart.GetIdOfPart(footerPart);
sectPr.Append(new FooterReference
{
Type = HeaderFooterValues.Default,
Id = footerRefId
});
}
// Title
if (!string.IsNullOrEmpty(title))
{
var titlePara = new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Title" }),
new Run(new Text(title)));
body.Append(titlePara);
}
// Author subtitle
if (!string.IsNullOrEmpty(author))
{
var authorPara = new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Subtitle" }),
new Run(new Text(author)));
body.Append(authorPara);
}
// TOC placeholder
if (tocPlaceholder)
{
body.Append(new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "TOCHeading" }),
new Run(new Text("Table of Contents"))));
// Insert TOC field
var tocPara = new Paragraph();
tocPara.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.Begin }));
tocPara.Append(new Run(new FieldCode(" TOC \\o \"1-3\" \\h \\z \\u ") { Space = SpaceProcessingModeValues.Preserve }));
tocPara.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.Separate }));
tocPara.Append(new Run(new Text("Update this field to generate table of contents.")));
tocPara.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.End }));
body.Append(tocPara);
// Page break after TOC
body.Append(new Paragraph(new Run(new Break { Type = BreakValues.Page })));
}
// Content from JSON (if provided)
if (!string.IsNullOrEmpty(contentJson) && File.Exists(contentJson))
{
var jsonContent = File.ReadAllText(contentJson);
AddContentFromJson(body, jsonContent, fontConfig);
}
// Ensure body has at least one paragraph
if (!body.Elements<Paragraph>().Any())
{
body.Append(new Paragraph());
}
// sectPr must be the last child of body
body.Append(sectPr);
mainPart.Document.Save();
Console.WriteLine($"Created {docType} document: {output}");
});
return cmd;
}
private static FontConfig GetFontConfig(string docType) => docType.ToLowerInvariant() switch
{
"letter" => FontDefaults.Letter,
"memo" => FontDefaults.Memo,
"academic" => FontDefaults.Academic,
_ => FontDefaults.Report,
};
private static Typography.PageSize GetPageSizeConfig(string name) => name.ToLowerInvariant() switch
{
"a4" => PageSizes.A4,
"legal" => PageSizes.Legal,
"a3" => PageSizes.A3,
_ => PageSizes.Letter,
};
private static MarginConfig GetMargins(string name) => name.ToLowerInvariant() switch
{
"narrow" => PageSizes.NarrowMargins,
"wide" => PageSizes.WideMargins,
_ => PageSizes.StandardMargins,
};
private static void AddDefaultStyles(MainDocumentPart mainPart, FontConfig fontConfig)
{
var stylesPart = mainPart.AddNewPart<StyleDefinitionsPart>();
var styles = new Styles();
// Default run properties
var defaultRPr = new StyleRunProperties(
new RunFonts { Ascii = fontConfig.BodyFont, HighAnsi = fontConfig.BodyFont },
new FontSize { Val = UnitConverter.FontSizeToSz(fontConfig.BodySize) },
new FontSizeComplexScript { Val = UnitConverter.FontSizeToSz(fontConfig.BodySize) });
// Normal style
styles.Append(new Style(
new StyleName { Val = "Normal" },
new PrimaryStyle(),
defaultRPr)
{ Type = StyleValues.Paragraph, StyleId = "Normal", Default = true });
// Heading styles 1-6
double[] headingSizes = [fontConfig.Heading1Size, fontConfig.Heading2Size, fontConfig.Heading3Size,
fontConfig.Heading4Size, fontConfig.Heading5Size, fontConfig.Heading6Size];
for (int i = 0; i < 6; i++)
{
var level = i + 1;
var headingStyle = new Style(
new StyleName { Val = $"heading {level}" },
new BasedOn { Val = "Normal" },
new NextParagraphStyle { Val = "Normal" },
new PrimaryStyle(),
new StyleParagraphProperties(
new KeepNext(),
new KeepLines(),
new SpacingBetweenLines { Before = "240", After = "120" },
new OutlineLevel { Val = i }),
new StyleRunProperties(
new RunFonts { Ascii = fontConfig.HeadingFont, HighAnsi = fontConfig.HeadingFont },
new FontSize { Val = UnitConverter.FontSizeToSz(headingSizes[i]) },
new FontSizeComplexScript { Val = UnitConverter.FontSizeToSz(headingSizes[i]) },
new Bold()))
{ Type = StyleValues.Paragraph, StyleId = $"Heading{level}" };
styles.Append(headingStyle);
}
// Title style
styles.Append(new Style(
new StyleName { Val = "Title" },
new BasedOn { Val = "Normal" },
new NextParagraphStyle { Val = "Normal" },
new PrimaryStyle(),
new StyleParagraphProperties(
new Justification { Val = JustificationValues.Center },
new SpacingBetweenLines { After = "300" }),
new StyleRunProperties(
new RunFonts { Ascii = fontConfig.HeadingFont, HighAnsi = fontConfig.HeadingFont },
new FontSize { Val = UnitConverter.FontSizeToSz(fontConfig.Heading1Size + 6) },
new FontSizeComplexScript { Val = UnitConverter.FontSizeToSz(fontConfig.Heading1Size + 6) }))
{ Type = StyleValues.Paragraph, StyleId = "Title" });
// Subtitle style
styles.Append(new Style(
new StyleName { Val = "Subtitle" },
new BasedOn { Val = "Normal" },
new NextParagraphStyle { Val = "Normal" },
new StyleParagraphProperties(
new Justification { Val = JustificationValues.Center },
new SpacingBetweenLines { After = "200" }),
new StyleRunProperties(
new Color { Val = "5A5A5A" },
new FontSize { Val = UnitConverter.FontSizeToSz(fontConfig.BodySize + 2) }))
{ Type = StyleValues.Paragraph, StyleId = "Subtitle" });
stylesPart.Styles = styles;
stylesPart.Styles.Save();
}
private static void AddContentFromJson(Body body, string jsonContent, FontConfig fontConfig)
{
// Simple JSON content format: array of {type, text, level?}
// e.g. [{"type":"heading","text":"Introduction","level":1},{"type":"paragraph","text":"..."}]
try
{
using var jsonDoc = System.Text.Json.JsonDocument.Parse(jsonContent);
foreach (var element in jsonDoc.RootElement.EnumerateArray())
{
var type = element.GetProperty("type").GetString() ?? "paragraph";
var text = element.GetProperty("text").GetString() ?? "";
switch (type)
{
case "heading":
var level = element.TryGetProperty("level", out var lvl) ? lvl.GetInt32() : 1;
level = Math.Clamp(level, 1, 6);
body.Append(new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = $"Heading{level}" }),
new Run(new Text(text))));
break;
case "paragraph":
body.Append(new Paragraph(new Run(new Text(text))));
break;
case "pagebreak":
body.Append(new Paragraph(new Run(new Break { Type = BreakValues.Page })));
break;
}
}
}
catch (System.Text.Json.JsonException ex)
{
Console.Error.WriteLine($"Warning: could not parse content JSON: {ex.Message}");
}
}
}
@@ -0,0 +1,155 @@
using System.CommandLine;
using System.IO.Compression;
using System.Text.Json;
using System.Xml.Linq;
namespace MiniMaxAIDocx.Core.Commands;
public static class DiffCommand
{
private static readonly XNamespace W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
public static Command Create()
{
var beforeOption = new Option<string>("--before") { Description = "Original DOCX", Required = true };
var afterOption = new Option<string>("--after") { Description = "Modified DOCX", Required = true };
var jsonOption = new Option<bool>("--json") { Description = "Output as JSON" };
var cmd = new Command("diff", "Compare two DOCX files")
{
beforeOption, afterOption, jsonOption
};
cmd.SetAction((parseResult) =>
{
var before = parseResult.GetValue(beforeOption)!;
var after = parseResult.GetValue(afterOption)!;
var asJson = parseResult.GetValue(jsonOption);
if (!File.Exists(before)) { Console.Error.WriteLine($"File not found: {before}"); return; }
if (!File.Exists(after)) { Console.Error.WriteLine($"File not found: {after}"); return; }
var beforeParas = ExtractParagraphs(before);
var afterParas = ExtractParagraphs(after);
var beforeStyles = ExtractStyleIds(before);
var afterStyles = ExtractStyleIds(after);
var beforeStructure = ExtractStructure(before);
var afterStructure = ExtractStructure(after);
// Text diff
var textChanges = new List<object>();
int maxLen = Math.Max(beforeParas.Count, afterParas.Count);
int changedParas = 0;
for (int i = 0; i < maxLen; i++)
{
var bText = i < beforeParas.Count ? beforeParas[i] : null;
var aText = i < afterParas.Count ? afterParas[i] : null;
if (bText != aText)
{
changedParas++;
textChanges.Add(new
{
paragraph = i + 1,
before = bText ?? "(absent)",
after = aText ?? "(absent)"
});
}
}
// Style diff
var addedStyles = afterStyles.Except(beforeStyles).ToList();
var removedStyles = beforeStyles.Except(afterStyles).ToList();
// Structure diff
var structureChanges = new List<string>();
if (beforeStructure.Sections != afterStructure.Sections)
structureChanges.Add($"Sections: {beforeStructure.Sections} -> {afterStructure.Sections}");
if (beforeStructure.Tables != afterStructure.Tables)
structureChanges.Add($"Tables: {beforeStructure.Tables} -> {afterStructure.Tables}");
if (beforeStructure.Images != afterStructure.Images)
structureChanges.Add($"Images: {beforeStructure.Images} -> {afterStructure.Images}");
var result = new
{
textChanges,
styleChanges = new { added = addedStyles, removed = removedStyles },
structureChanges,
summary = $"{changedParas} paragraphs changed, {addedStyles.Count + removedStyles.Count} styles modified, {structureChanges.Count} structural changes"
};
if (asJson)
{
Console.WriteLine(JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }));
}
else
{
Console.WriteLine(result.summary);
Console.WriteLine();
if (textChanges.Count > 0)
{
Console.WriteLine($"Text changes ({textChanges.Count}):");
foreach (var tc in textChanges.Take(20))
Console.WriteLine($" {tc}");
if (textChanges.Count > 20)
Console.WriteLine($" ... and {textChanges.Count - 20} more");
}
if (addedStyles.Count > 0)
Console.WriteLine($"Added styles: {string.Join(", ", addedStyles)}");
if (removedStyles.Count > 0)
Console.WriteLine($"Removed styles: {string.Join(", ", removedStyles)}");
foreach (var sc in structureChanges)
Console.WriteLine($"Structure: {sc}");
}
});
return cmd;
}
private static List<string> ExtractParagraphs(string docxPath)
{
using var zip = ZipFile.OpenRead(docxPath);
var entry = zip.GetEntry("word/document.xml");
if (entry == null) return new();
using var stream = entry.Open();
var doc = XDocument.Load(stream);
return doc.Descendants(W + "p")
.Select(p => string.Concat(p.Descendants(W + "t").Select(t => t.Value)))
.ToList();
}
private static HashSet<string> ExtractStyleIds(string docxPath)
{
using var zip = ZipFile.OpenRead(docxPath);
var entry = zip.GetEntry("word/styles.xml");
if (entry == null) return new();
using var stream = entry.Open();
var doc = XDocument.Load(stream);
return doc.Descendants(W + "style")
.Select(s => (string?)s.Attribute(W + "styleId"))
.Where(id => id != null)
.ToHashSet()!;
}
private record StructureInfo(int Sections, int Tables, int Images);
private static StructureInfo ExtractStructure(string docxPath)
{
using var zip = ZipFile.OpenRead(docxPath);
var entry = zip.GetEntry("word/document.xml");
if (entry == null) return new(0, 0, 0);
using var stream = entry.Open();
var doc = XDocument.Load(stream);
return new(
doc.Descendants(W + "sectPr").Count(),
doc.Descendants(W + "tbl").Count(),
doc.Descendants(W + "drawing").Count()
);
}
}
@@ -0,0 +1,487 @@
using System.CommandLine;
using System.Text.RegularExpressions;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using MiniMaxAIDocx.Core.OpenXml;
namespace MiniMaxAIDocx.Core.Commands;
/// <summary>
/// Scenario B: Surgical content editing operations on existing DOCX files.
/// Preserves all existing formatting and minimizes XML changes.
/// </summary>
public static class EditContentCommand
{
public static Command Create()
{
var cmd = new Command("edit", "Edit existing DOCX content");
cmd.Add(CreateReplaceTextCommand());
cmd.Add(CreateFillTableCommand());
cmd.Add(CreateInsertParagraphCommand());
cmd.Add(CreateUpdateFieldCommand());
cmd.Add(CreateListPlaceholdersCommand());
cmd.Add(CreateFillPlaceholdersCommand());
return cmd;
}
private static Command CreateReplaceTextCommand()
{
var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
var outputOpt = new Option<string>("--output") { Description = "Output file path (defaults to overwriting input)" };
var searchOpt = new Option<string>("--search") { Description = "Text to search for", Required = true };
var replaceOpt = new Option<string>("--replace") { Description = "Replacement text", Required = true };
var regexOpt = new Option<bool>("--regex") { Description = "Treat search as a regex pattern" };
var cmd = new Command("replace-text", "Replace text while preserving formatting")
{
inputOpt, outputOpt, searchOpt, replaceOpt, regexOpt
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOpt)!;
var output = parseResult.GetValue(outputOpt) ?? input;
var search = parseResult.GetValue(searchOpt)!;
var replace = parseResult.GetValue(replaceOpt)!;
var useRegex = parseResult.GetValue(regexOpt);
if (output != input) File.Copy(input, output, overwrite: true);
using var doc = WordprocessingDocument.Open(output, true);
var body = doc.MainDocumentPart?.Document.Body;
if (body == null) { Console.Error.WriteLine("No document body found."); return; }
int count = 0;
foreach (var paragraph in body.Descendants<Paragraph>())
{
count += ReplaceInParagraph(paragraph, search, replace, useRegex);
}
doc.MainDocumentPart!.Document.Save();
Console.WriteLine($"Replaced {count} occurrence(s) in {output}");
});
return cmd;
}
private static Command CreateFillTableCommand()
{
var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
var outputOpt = new Option<string>("--output") { Description = "Output file path" };
var tableIndexOpt = new Option<int>("--table-index") { Description = "Zero-based index of the table to fill" };
tableIndexOpt.DefaultValueFactory = _ => 0;
var csvOpt = new Option<string>("--csv") { Description = "CSV file with data to fill", Required = true };
var appendOpt = new Option<bool>("--append") { Description = "Append rows instead of replacing existing data rows" };
var cmd = new Command("fill-table", "Fill a table with data from CSV")
{
inputOpt, outputOpt, tableIndexOpt, csvOpt, appendOpt
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOpt)!;
var output = parseResult.GetValue(outputOpt) ?? input;
var tableIndex = parseResult.GetValue(tableIndexOpt);
var csvPath = parseResult.GetValue(csvOpt)!;
var append = parseResult.GetValue(appendOpt);
if (output != input) File.Copy(input, output, overwrite: true);
if (!File.Exists(csvPath)) { Console.Error.WriteLine($"CSV file not found: {csvPath}"); return; }
using var doc = WordprocessingDocument.Open(output, true);
var body = doc.MainDocumentPart?.Document.Body;
if (body == null) { Console.Error.WriteLine("No document body found."); return; }
var tables = body.Elements<Table>().ToList();
if (tableIndex >= tables.Count)
{
Console.Error.WriteLine($"Table index {tableIndex} out of range (found {tables.Count} tables).");
return;
}
var table = tables[tableIndex];
var csvLines = File.ReadAllLines(csvPath);
if (csvLines.Length == 0) { Console.WriteLine("CSV is empty, nothing to fill."); return; }
// Get template row properties from the first data row (second row, after header)
var existingRows = table.Elements<TableRow>().ToList();
TableRow? templateRow = existingRows.Count > 1 ? existingRows[1] : existingRows.FirstOrDefault();
var templateTrPr = templateRow?.TableRowProperties?.CloneNode(true) as TableRowProperties;
if (!append)
{
// Remove all rows except the header row
for (int i = existingRows.Count - 1; i >= 1; i--)
existingRows[i].Remove();
}
int rowsAdded = 0;
// Skip header line in CSV (index 0)
for (int i = 1; i < csvLines.Length; i++)
{
var values = ParseCsvLine(csvLines[i]);
var newRow = new TableRow();
if (templateTrPr != null)
newRow.Append(templateTrPr.CloneNode(true));
foreach (var val in values)
{
var cell = new TableCell(
new Paragraph(new Run(new Text(val))));
newRow.Append(cell);
}
table.Append(newRow);
rowsAdded++;
}
doc.MainDocumentPart!.Document.Save();
Console.WriteLine($"Added {rowsAdded} rows to table {tableIndex} in {output}");
});
return cmd;
}
private static Command CreateInsertParagraphCommand()
{
var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
var outputOpt = new Option<string>("--output") { Description = "Output file path" };
var textOpt = new Option<string>("--text") { Description = "Paragraph text", Required = true };
var styleOpt = new Option<string>("--style") { Description = "Paragraph style (e.g. Heading1, Normal)" };
var afterOpt = new Option<int>("--after-paragraph") { Description = "Insert after this paragraph index (0-based)" };
afterOpt.DefaultValueFactory = _ => -1; // -1 = append at end
var cmd = new Command("insert-paragraph", "Insert a new paragraph")
{
inputOpt, outputOpt, textOpt, styleOpt, afterOpt
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOpt)!;
var output = parseResult.GetValue(outputOpt) ?? input;
var text = parseResult.GetValue(textOpt)!;
var style = parseResult.GetValue(styleOpt);
var afterIndex = parseResult.GetValue(afterOpt);
if (output != input) File.Copy(input, output, overwrite: true);
using var doc = WordprocessingDocument.Open(output, true);
var body = doc.MainDocumentPart?.Document.Body;
if (body == null) { Console.Error.WriteLine("No document body found."); return; }
var newPara = new Paragraph();
if (!string.IsNullOrEmpty(style))
newPara.Append(new ParagraphProperties(new ParagraphStyleId { Val = style }));
newPara.Append(new Run(new Text(text)));
var paragraphs = body.Elements<Paragraph>().ToList();
if (afterIndex >= 0 && afterIndex < paragraphs.Count)
{
paragraphs[afterIndex].InsertAfterSelf(newPara);
}
else
{
// Insert before sectPr if present, otherwise append
var sectPr = body.Elements<SectionProperties>().FirstOrDefault();
if (sectPr != null)
sectPr.InsertBeforeSelf(newPara);
else
body.Append(newPara);
}
doc.MainDocumentPart!.Document.Save();
Console.WriteLine($"Inserted paragraph in {output}");
});
return cmd;
}
private static Command CreateUpdateFieldCommand()
{
var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
var outputOpt = new Option<string>("--output") { Description = "Output file path" };
var fieldNameOpt = new Option<string>("--field") { Description = "Document property field name (e.g. TITLE, AUTHOR)", Required = true };
var valueOpt = new Option<string>("--value") { Description = "New field value", Required = true };
var cmd = new Command("update-field", "Update a document property field value")
{
inputOpt, outputOpt, fieldNameOpt, valueOpt
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOpt)!;
var output = parseResult.GetValue(outputOpt) ?? input;
var fieldName = parseResult.GetValue(fieldNameOpt)!;
var value = parseResult.GetValue(valueOpt)!;
if (output != input) File.Copy(input, output, overwrite: true);
using var doc = WordprocessingDocument.Open(output, true);
// Update core properties
var props = doc.PackageProperties;
switch (fieldName.ToUpperInvariant())
{
case "TITLE": props.Title = value; break;
case "AUTHOR": props.Creator = value; break;
case "SUBJECT": props.Subject = value; break;
case "KEYWORDS": props.Keywords = value; break;
case "DESCRIPTION": props.Description = value; break;
case "CATEGORY": props.Category = value; break;
default:
Console.Error.WriteLine($"Unknown field: {fieldName}. Supported: TITLE, AUTHOR, SUBJECT, KEYWORDS, DESCRIPTION, CATEGORY");
return;
}
Console.WriteLine($"Updated {fieldName} to \"{value}\" in {output}");
});
return cmd;
}
private static Command CreateListPlaceholdersCommand()
{
var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
var patternOpt = new Option<string>("--pattern") { Description = "Placeholder pattern (regex)" };
patternOpt.DefaultValueFactory = _ => @"\{\{(\w+)\}\}"; // {{PLACEHOLDER}}
var cmd = new Command("list-placeholders", "List all placeholders found in the document")
{
inputOpt, patternOpt
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOpt)!;
var pattern = parseResult.GetValue(patternOpt)!;
using var doc = WordprocessingDocument.Open(input, false);
var body = doc.MainDocumentPart?.Document.Body;
if (body == null) { Console.Error.WriteLine("No document body found."); return; }
var placeholders = new HashSet<string>();
var regex = new Regex(pattern);
foreach (var paragraph in body.Descendants<Paragraph>())
{
var fullText = string.Concat(paragraph.Descendants<Text>().Select(t => t.Text));
foreach (Match match in regex.Matches(fullText))
{
placeholders.Add(match.Value);
}
}
if (placeholders.Count == 0)
{
Console.WriteLine("No placeholders found.");
return;
}
Console.WriteLine($"Found {placeholders.Count} unique placeholder(s):");
foreach (var p in placeholders.OrderBy(x => x))
Console.WriteLine($" {p}");
});
return cmd;
}
private static Command CreateFillPlaceholdersCommand()
{
var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
var outputOpt = new Option<string>("--output") { Description = "Output file path" };
var mappingOpt = new Option<string>("--mapping") { Description = "JSON file mapping placeholder names to values", Required = true };
var patternOpt = new Option<string>("--pattern") { Description = "Placeholder pattern with capture group for the name" };
patternOpt.DefaultValueFactory = _ => @"\{\{(\w+)\}\}";
var cmd = new Command("fill-placeholders", "Replace placeholders with values from a mapping file")
{
inputOpt, outputOpt, mappingOpt, patternOpt
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOpt)!;
var output = parseResult.GetValue(outputOpt) ?? input;
var mappingPath = parseResult.GetValue(mappingOpt)!;
var pattern = parseResult.GetValue(patternOpt)!;
if (!File.Exists(mappingPath)) { Console.Error.WriteLine($"Mapping file not found: {mappingPath}"); return; }
var mappingJson = File.ReadAllText(mappingPath);
Dictionary<string, string> mapping;
try
{
mapping = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(mappingJson) ?? [];
}
catch (System.Text.Json.JsonException ex)
{
Console.Error.WriteLine($"Invalid mapping JSON: {ex.Message}");
return;
}
if (output != input) File.Copy(input, output, overwrite: true);
using var doc = WordprocessingDocument.Open(output, true);
var body = doc.MainDocumentPart?.Document.Body;
if (body == null) { Console.Error.WriteLine("No document body found."); return; }
int totalReplacements = 0;
var regex = new Regex(pattern);
foreach (var paragraph in body.Descendants<Paragraph>())
{
var fullText = string.Concat(paragraph.Descendants<Text>().Select(t => t.Text));
var matches = regex.Matches(fullText);
if (matches.Count == 0) continue;
foreach (Match match in matches)
{
var placeholderName = match.Groups.Count > 1 ? match.Groups[1].Value : match.Value;
if (mapping.TryGetValue(placeholderName, out var replacement))
{
totalReplacements += ReplaceInParagraph(paragraph, match.Value, replacement, false);
}
}
}
doc.MainDocumentPart!.Document.Save();
Console.WriteLine($"Filled {totalReplacements} placeholder(s) in {output}");
});
return cmd;
}
/// <summary>
/// Replaces text within a paragraph while preserving run formatting.
/// Handles the case where search text may span multiple runs.
/// </summary>
private static int ReplaceInParagraph(Paragraph paragraph, string search, string replace, bool useRegex)
{
var runs = paragraph.Elements<Run>().ToList();
if (runs.Count == 0) return 0;
// Build the full paragraph text and a map from character index to (run, position within run)
var fullText = string.Concat(runs.SelectMany(r => r.Elements<Text>().Select(t => t.Text)));
if (string.IsNullOrEmpty(fullText)) return 0;
int count = 0;
if (!useRegex)
{
// Simple case: search within each run first
foreach (var run in runs)
{
foreach (var textElement in run.Elements<Text>().ToList())
{
if (textElement.Text.Contains(search))
{
var newText = textElement.Text.Replace(search, replace);
count += (textElement.Text.Length - newText.Length + replace.Length - search.Length) == 0 ? 0 :
CountOccurrences(textElement.Text, search);
textElement.Text = newText;
if (newText.StartsWith(' ') || newText.EndsWith(' '))
textElement.Space = SpaceProcessingModeValues.Preserve;
}
}
}
// Handle cross-run matches by concatenating all runs, replacing, and rebuilding
if (count == 0 && fullText.Contains(search))
{
var newFullText = fullText.Replace(search, replace);
count = CountOccurrences(fullText, search);
RebuildRunsWithText(paragraph, runs, newFullText);
}
}
else
{
var regex = new Regex(search);
if (regex.IsMatch(fullText))
{
count = regex.Matches(fullText).Count;
var newFullText = regex.Replace(fullText, replace);
RebuildRunsWithText(paragraph, runs, newFullText);
}
}
return count;
}
/// <summary>
/// Replaces the text content of existing runs with new text,
/// preserving the formatting of the first run.
/// </summary>
private static void RebuildRunsWithText(Paragraph paragraph, List<Run> runs, string newText)
{
if (runs.Count == 0) return;
// Keep the first run's formatting, set its text to the full new text
var firstRun = runs[0];
var firstText = firstRun.Elements<Text>().FirstOrDefault();
if (firstText != null)
{
firstText.Text = newText;
if (newText.StartsWith(' ') || newText.EndsWith(' '))
firstText.Space = SpaceProcessingModeValues.Preserve;
}
// Remove all other runs
for (int i = 1; i < runs.Count; i++)
runs[i].Remove();
}
private static int CountOccurrences(string text, string search)
{
int count = 0;
int index = 0;
while ((index = text.IndexOf(search, index, StringComparison.Ordinal)) != -1)
{
count++;
index += search.Length;
}
return count;
}
private static string[] ParseCsvLine(string line)
{
// Simple CSV parser (handles quoted fields)
var result = new List<string>();
bool inQuotes = false;
var current = new System.Text.StringBuilder();
for (int i = 0; i < line.Length; i++)
{
char c = line[i];
if (c == '"')
{
if (inQuotes && i + 1 < line.Length && line[i + 1] == '"')
{
current.Append('"');
i++;
}
else
{
inQuotes = !inQuotes;
}
}
else if (c == ',' && !inQuotes)
{
result.Add(current.ToString());
current.Clear();
}
else
{
current.Append(c);
}
}
result.Add(current.ToString());
return result.ToArray();
}
}
@@ -0,0 +1,108 @@
using System.CommandLine;
using System.IO.Compression;
using System.Xml.Linq;
namespace MiniMaxAIDocx.Core.Commands;
public static class FixOrderCommand
{
private static readonly XNamespace W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
// Canonical element ordering within common parent elements per ISO 29500
private static readonly Dictionary<string, List<string>> ElementOrder = new()
{
["pPr"] = new() { "pStyle", "keepNext", "keepLines", "pageBreakBefore", "widowControl", "numPr", "suppressLineNumbers", "pBdr", "shd", "tabs", "suppressAutoHyphens", "spacing", "ind", "jc", "outlineLvl", "rPr" },
["rPr"] = new() { "rStyle", "rFonts", "b", "bCs", "i", "iCs", "caps", "smallCaps", "strike", "dstrike", "vanish", "color", "spacing", "w", "kern", "position", "sz", "szCs", "highlight", "u", "effect", "vertAlign", "lang" },
["tblPr"] = new() { "tblStyle", "tblpPr", "tblOverlap", "tblW", "jc", "tblInd", "tblBorders", "shd", "tblLayout", "tblCellMar", "tblLook" },
["tcPr"] = new() { "cnfStyle", "tcW", "gridSpan", "hMerge", "vMerge", "tcBorders", "shd", "noWrap", "tcMar", "textDirection", "tcFitText", "vAlign" },
["sectPr"] = new() { "headerReference", "footerReference", "footnotePr", "endnotePr", "type", "pgSz", "pgMar", "paperSrc", "pgBorders", "lnNumType", "pgNumType", "cols", "docGrid" },
};
public static Command Create()
{
var inputOption = new Option<string>("--input") { Description = "DOCX file to fix", Required = true };
var outputOption = new Option<string>("--output") { Description = "Output path (default: overwrite input)" };
var backupOption = new Option<bool>("--backup") { Description = "Create .bak before modifying", DefaultValueFactory = (_) => true };
var cmd = new Command("fix-order", "Fix OpenXML element ordering per ISO 29500")
{
inputOption, outputOption, backupOption
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOption)!;
var output = parseResult.GetValue(outputOption) ?? input;
var backup = parseResult.GetValue(backupOption);
if (!File.Exists(input))
{
Console.Error.WriteLine($"File not found: {input}");
return;
}
if (backup && output == input)
File.Copy(input, input + ".bak", true);
var tempPath = Path.GetTempFileName();
File.Copy(input, tempPath, true);
using var zip = ZipFile.Open(tempPath, ZipArchiveMode.Update);
var entry = zip.GetEntry("word/document.xml");
if (entry == null)
{
Console.Error.WriteLine("Not a valid DOCX");
return;
}
XDocument doc;
using (var stream = entry.Open())
doc = XDocument.Load(stream);
int reorderedCount = 0;
foreach (var (parentName, order) in ElementOrder)
{
foreach (var parent in doc.Descendants(W + parentName))
{
var children = parent.Elements().ToList();
var sorted = children.OrderBy(e =>
{
var idx = order.IndexOf(e.Name.LocalName);
return idx >= 0 ? idx : order.Count;
}).ToList();
bool changed = false;
for (int i = 0; i < children.Count; i++)
{
if (children[i] != sorted[i])
{
changed = true;
break;
}
}
if (changed)
{
parent.ReplaceNodes(sorted);
reorderedCount++;
}
}
}
entry.Delete();
var newEntry = zip.CreateEntry("word/document.xml", CompressionLevel.Optimal);
using (var stream = newEntry.Open())
doc.Save(stream);
zip.Dispose();
File.Copy(tempPath, output, true);
File.Delete(tempPath);
Console.WriteLine($"Reordered {reorderedCount} element group(s)");
Console.WriteLine($"Written to: {output}");
});
return cmd;
}
}
@@ -0,0 +1,122 @@
using System.CommandLine;
using System.IO.Compression;
using System.Xml.Linq;
namespace MiniMaxAIDocx.Core.Commands;
public static class MergeRunsCommand
{
private static readonly XNamespace W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
public static Command Create()
{
var inputOption = new Option<string>("--input") { Description = "DOCX file to optimize", Required = true };
var outputOption = new Option<string>("--output") { Description = "Output path (default: overwrite input)" };
var dryRunOption = new Option<bool>("--dry-run") { Description = "Report without modifying" };
var cmd = new Command("merge-runs", "Merge adjacent runs with identical formatting")
{
inputOption, outputOption, dryRunOption
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOption)!;
var output = parseResult.GetValue(outputOption) ?? input;
var dryRun = parseResult.GetValue(dryRunOption);
if (!File.Exists(input))
{
Console.Error.WriteLine($"File not found: {input}");
return;
}
var tempPath = Path.GetTempFileName();
File.Copy(input, tempPath, true);
using var zip = ZipFile.Open(tempPath, ZipArchiveMode.Update);
var entry = zip.GetEntry("word/document.xml");
if (entry == null)
{
Console.Error.WriteLine("Not a valid DOCX: missing word/document.xml");
return;
}
XDocument doc;
using (var stream = entry.Open())
doc = XDocument.Load(stream);
int originalCount = 0;
int mergedCount = 0;
foreach (var p in doc.Descendants(W + "p"))
{
var runs = p.Elements(W + "r").ToList();
originalCount += runs.Count;
for (int i = runs.Count - 1; i > 0; i--)
{
var current = runs[i];
var previous = runs[i - 1];
var curProps = current.Element(W + "rPr")?.ToString() ?? "";
var prevProps = previous.Element(W + "rPr")?.ToString() ?? "";
if (curProps == prevProps)
{
// Only merge if both contain only text elements
var curChildren = current.Elements().Where(e => e.Name != W + "rPr").ToList();
var prevChildren = previous.Elements().Where(e => e.Name != W + "rPr").ToList();
if (curChildren.All(e => e.Name == W + "t") && prevChildren.All(e => e.Name == W + "t"))
{
var prevText = previous.Elements(W + "t").LastOrDefault();
var curText = current.Elements(W + "t").FirstOrDefault();
if (prevText != null && curText != null)
{
prevText.Value += curText.Value;
prevText.SetAttributeValue(XNamespace.Xml + "space", "preserve");
foreach (var extra in current.Elements(W + "t").Skip(1))
{
previous.Add(new XElement(extra));
}
current.Remove();
runs.RemoveAt(i);
}
}
}
}
mergedCount += runs.Count;
}
if (dryRun)
{
Console.WriteLine($"Original runs: {originalCount}");
Console.WriteLine($"After merge: {mergedCount}");
Console.WriteLine($"Reduction: {(originalCount > 0 ? (originalCount - mergedCount) * 100.0 / originalCount : 0):F1}%");
File.Delete(tempPath);
return;
}
entry.Delete();
var newEntry = zip.CreateEntry("word/document.xml", CompressionLevel.Optimal);
using (var stream = newEntry.Open())
doc.Save(stream);
zip.Dispose();
File.Copy(tempPath, output, true);
File.Delete(tempPath);
Console.WriteLine($"Original runs: {originalCount}");
Console.WriteLine($"After merge: {mergedCount}");
Console.WriteLine($"Reduction: {(originalCount > 0 ? (originalCount - mergedCount) * 100.0 / originalCount : 0):F1}%");
Console.WriteLine($"Written to: {output}");
});
return cmd;
}
}
@@ -0,0 +1,107 @@
using System.CommandLine;
using System.Text.Json;
using MiniMaxAIDocx.Core.Validation;
namespace MiniMaxAIDocx.Core.Commands;
public static class ValidateCommand
{
public static Command Create()
{
var inputOption = new Option<string>("--input") { Description = "DOCX file to validate", Required = true };
var xsdOption = new Option<string>("--xsd") { Description = "XSD schema path for XML validation" };
var businessOption = new Option<bool>("--business") { Description = "Run business rule validation" };
var gateCheckOption = new Option<string>("--gate-check") { Description = "Template DOCX for gate-check validation" };
var jsonOption = new Option<bool>("--json") { Description = "Output results as JSON" };
var cmd = new Command("validate", "Validate DOCX structure and content")
{
inputOption, xsdOption, businessOption, gateCheckOption, jsonOption
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOption)!;
var xsd = parseResult.GetValue(xsdOption);
var business = parseResult.GetValue(businessOption);
var gateCheck = parseResult.GetValue(gateCheckOption);
var asJson = parseResult.GetValue(jsonOption);
if (!File.Exists(input))
{
Console.Error.WriteLine($"File not found: {input}");
return;
}
var combinedResult = new ValidationResult();
GateCheckResult? gateResult = null;
if (xsd != null)
{
var xsdValidator = new XsdValidator();
combinedResult.Merge(xsdValidator.Validate(input, xsd));
}
if (business)
{
var bizValidator = new BusinessRuleValidator();
combinedResult.Merge(bizValidator.Validate(input));
}
if (gateCheck != null)
{
var gateValidator = new GateCheckValidator();
gateResult = gateValidator.Validate(input, gateCheck);
}
if (asJson)
{
var output = new
{
isValid = combinedResult.IsValid && (gateResult?.Passed ?? true),
errors = combinedResult.Errors,
warnings = combinedResult.Warnings,
gateCheck = gateResult == null ? null : new
{
passed = gateResult.Passed,
violations = gateResult.Violations
}
};
Console.WriteLine(JsonSerializer.Serialize(output, new JsonSerializerOptions { WriteIndented = true }));
}
else
{
if (combinedResult.Errors.Count > 0)
{
Console.WriteLine($"ERRORS ({combinedResult.Errors.Count}):");
foreach (var e in combinedResult.Errors)
Console.WriteLine($" [{e.Severity}] {e.Message}" + (e.LineNumber > 0 ? $" (line {e.LineNumber}:{e.LinePosition})" : ""));
}
if (combinedResult.Warnings.Count > 0)
{
Console.WriteLine($"WARNINGS ({combinedResult.Warnings.Count}):");
foreach (var w in combinedResult.Warnings)
Console.WriteLine($" [{w.Severity}] {w.Message}");
}
if (gateResult != null)
{
Console.WriteLine(gateResult.Passed ? "GATE CHECK: PASSED" : "GATE CHECK: FAILED");
foreach (var v in gateResult.Violations)
Console.WriteLine($" - {v}");
}
if (combinedResult.IsValid && (gateResult?.Passed ?? true))
Console.WriteLine("Validation: PASSED");
else
Console.WriteLine("Validation: FAILED");
}
if (!combinedResult.IsValid || gateResult is { Passed: false })
Environment.ExitCode = 1;
});
return cmd;
}
}
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NeutralLanguage>en</NeutralLanguage>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DocumentFormat.OpenXml" Version="3.5.1" />
<PackageReference Include="System.CommandLine" Version="2.0.5" />
</ItemGroup>
</Project>
@@ -0,0 +1,169 @@
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
namespace MiniMaxAIDocx.Core.OpenXml;
/// <summary>
/// Manages the 4-file comment system (comments.xml, commentsExtended.xml,
/// commentsIds.xml, commentsExtensible.xml) plus document.xml markers.
/// </summary>
public static class CommentSynchronizer
{
/// <summary>
/// Adds a comment to the document, updating all required parts.
/// </summary>
public static int AddComment(WordprocessingDocument doc, string text, string author, string rangeBookmark)
{
var mainPart = doc.MainDocumentPart
?? throw new InvalidOperationException("Document has no main part.");
int commentId = GetNextCommentId(doc);
// Ensure comments part exists
var commentsPart = mainPart.WordprocessingCommentsPart
?? mainPart.AddNewPart<WordprocessingCommentsPart>();
if (commentsPart.Comments == null)
commentsPart.Comments = new Comments();
// Create the comment
var comment = new Comment
{
Id = commentId.ToString(),
Author = author,
Date = DateTime.UtcNow,
Initials = author.Length > 0 ? author[..1].ToUpperInvariant() : "A"
};
comment.Append(new Paragraph(new Run(new Text(text))));
commentsPart.Comments.Append(comment);
// Add range markers in document body
var body = mainPart.Document.Body;
if (body != null)
{
// Find bookmark or append at end
var rangeStart = new CommentRangeStart { Id = commentId.ToString() };
var rangeEnd = new CommentRangeEnd { Id = commentId.ToString() };
var reference = new Run(new CommentReference { Id = commentId.ToString() });
body.Append(rangeStart);
body.Append(rangeEnd);
body.Append(new Paragraph(reference));
}
return commentId;
}
/// <summary>
/// Adds a reply to an existing comment.
/// </summary>
public static int AddReply(WordprocessingDocument doc, int parentCommentId, string text, string author)
{
var mainPart = doc.MainDocumentPart
?? throw new InvalidOperationException("Document has no main part.");
var commentsPart = mainPart.WordprocessingCommentsPart
?? throw new InvalidOperationException("Document has no comments part.");
int replyId = GetNextCommentId(doc);
var reply = new Comment
{
Id = replyId.ToString(),
Author = author,
Date = DateTime.UtcNow,
Initials = author.Length > 0 ? author[..1].ToUpperInvariant() : "A"
};
reply.Append(new Paragraph(new Run(new Text(text))));
commentsPart.Comments?.Append(reply);
// Link reply to parent via commentsExtended.xml
LinkReplyToParent(doc, replyId, parentCommentId);
return replyId;
}
/// <summary>
/// Marks a comment as resolved/done by setting done="1" in commentsExtended.xml.
/// Uses raw XML manipulation since these extended parts lack typed SDK support.
/// </summary>
public static void ResolveComment(WordprocessingDocument doc, int commentId)
{
var mainPart = doc.MainDocumentPart;
if (mainPart == null) return;
// commentsExtended.xml is an untyped part — manipulate via raw XML
const string ceUri = "http://schemas.microsoft.com/office/word/2018/wordml/cex";
foreach (var part in mainPart.Parts)
{
if (part.OpenXmlPart.ContentType.Contains("commentsExtensible"))
{
using var stream = part.OpenXmlPart.GetStream(FileMode.Open, FileAccess.ReadWrite);
var xdoc = System.Xml.Linq.XDocument.Load(stream);
var ns = System.Xml.Linq.XNamespace.Get(ceUri);
var commentEl = xdoc.Descendants(ns + "comment")
.FirstOrDefault(e => e.Attribute(ns + "paraId")?.Value != null);
// Set done flag if element found for this comment
if (commentEl != null)
{
commentEl.SetAttributeValue("done", "1");
stream.SetLength(0);
xdoc.Save(stream);
}
return;
}
}
}
/// <summary>
/// Links a reply comment to its parent via commentsExtended.xml (w15:commentEx).
/// Uses raw XML since the extended comment parts lack typed SDK support.
/// </summary>
private static void LinkReplyToParent(WordprocessingDocument doc, int replyId, int parentCommentId)
{
var mainPart = doc.MainDocumentPart;
if (mainPart == null) return;
const string w15Uri = "http://schemas.microsoft.com/office/word/2012/wordml";
var w15 = System.Xml.Linq.XNamespace.Get(w15Uri);
// Find or create commentsExtended part
foreach (var part in mainPart.Parts)
{
if (part.OpenXmlPart.ContentType.Contains("commentsExtended"))
{
using var stream = part.OpenXmlPart.GetStream(FileMode.Open, FileAccess.ReadWrite);
var xdoc = System.Xml.Linq.XDocument.Load(stream);
var root = xdoc.Root;
if (root == null) return;
root.Add(new System.Xml.Linq.XElement(w15 + "commentEx",
new System.Xml.Linq.XAttribute(w15 + "paraId", replyId.ToString("X8")),
new System.Xml.Linq.XAttribute(w15 + "paraIdParent", parentCommentId.ToString("X8")),
new System.Xml.Linq.XAttribute(w15 + "done", "0")));
stream.SetLength(0);
xdoc.Save(stream);
return;
}
}
}
/// <summary>
/// Finds the maximum existing comment ID and returns the next one.
/// </summary>
public static int GetNextCommentId(WordprocessingDocument doc)
{
var commentsPart = doc.MainDocumentPart?.WordprocessingCommentsPart;
if (commentsPart?.Comments == null) return 1;
int maxId = 0;
foreach (var comment in commentsPart.Comments.Elements<Comment>())
{
if (comment.Id?.Value != null && int.TryParse(comment.Id.Value, out int id) && id > maxId)
maxId = id;
}
return maxId + 1;
}
}
@@ -0,0 +1,80 @@
using System.Xml.Linq;
namespace MiniMaxAIDocx.Core.OpenXml;
/// <summary>
/// Defines canonical child element ordering for key OpenXML parent elements
/// and provides reordering utilities.
/// </summary>
public static class ElementOrder
{
private static readonly Dictionary<string, string[]> OrderMap = new()
{
["w:body"] = ["w:p", "w:tbl", "w:sdt", "w:sectPr"],
["w:p"] = ["w:pPr", "w:hyperlink", "w:r", "w:ins", "w:del", "w:bookmarkStart", "w:bookmarkEnd", "w:commentRangeStart", "w:commentRangeEnd", "w:fldSimple"],
["w:pPr"] = ["w:pStyle", "w:keepNext", "w:keepLines", "w:pageBreakBefore", "w:widowControl", "w:numPr", "w:pBdr", "w:shd", "w:tabs", "w:suppressAutoHyphens", "w:spacing", "w:ind", "w:jc", "w:rPr", "w:sectPr", "w:pPrChange"],
["w:r"] = ["w:rPr", "w:t", "w:br", "w:tab", "w:cr", "w:sym", "w:drawing", "w:delText", "w:fldChar", "w:instrText", "w:lastRenderedPageBreak", "w:noBreakHyphen", "w:softHyphen"],
["w:rPr"] = ["w:rStyle", "w:rFonts", "w:b", "w:bCs", "w:i", "w:iCs", "w:caps", "w:smallCaps", "w:strike", "w:dstrike", "w:vanish", "w:color", "w:sz", "w:szCs", "w:u", "w:shd", "w:highlight", "w:lang", "w:rPrChange"],
["w:tbl"] = ["w:tblPr", "w:tblGrid", "w:tr"],
["w:tblPr"] = ["w:tblStyle", "w:tblpPr", "w:tblOverlap", "w:tblW", "w:jc", "w:tblCellSpacing", "w:tblInd", "w:tblBorders", "w:shd", "w:tblLayout", "w:tblCellMar", "w:tblLook", "w:tblPrChange"],
["w:tr"] = ["w:trPr", "w:tc"],
["w:trPr"] = ["w:cnfStyle", "w:divId", "w:gridBefore", "w:gridAfter", "w:wBefore", "w:wAfter", "w:cantSplit", "w:trHeight", "w:tblHeader", "w:tblCellSpacing", "w:jc", "w:hidden", "w:ins", "w:del", "w:trPrChange"],
["w:tc"] = ["w:tcPr", "w:p", "w:tbl"],
["w:tcPr"] = ["w:cnfStyle", "w:tcW", "w:gridSpan", "w:hMerge", "w:vMerge", "w:tcBorders", "w:shd", "w:noWrap", "w:tcMar", "w:textDirection", "w:tcFitText", "w:vAlign", "w:hideMark", "w:headers", "w:cellIns", "w:cellDel", "w:cellMerge", "w:tcPrChange"],
["w:sectPr"] = ["w:headerReference", "w:footerReference", "w:type", "w:pgSz", "w:pgMar", "w:paperSrc", "w:pgBorders", "w:lnNumType", "w:pgNumType", "w:cols", "w:formProt", "w:vAlign", "w:noEndnote", "w:titlePg", "w:textDirection", "w:bidi", "w:rtlGutter", "w:docGrid"],
["w:hdr"] = ["w:p", "w:tbl", "w:sdt"],
["w:ftr"] = ["w:p", "w:tbl", "w:sdt"],
};
/// <summary>
/// Returns the canonical child ordering for a given parent element name (e.g. "w:p").
/// Returns null if no ordering is defined.
/// </summary>
public static string[]? GetChildOrder(string parentElement)
{
return OrderMap.TryGetValue(parentElement, out var order) ? order : null;
}
/// <summary>
/// Reorders children of the given XElement according to the canonical ordering rules.
/// Children not listed in the ordering are placed at the end in their original order.
/// </summary>
public static void ReorderChildren(XElement parent)
{
var qualifiedName = GetQualifiedName(parent);
var order = GetChildOrder(qualifiedName);
if (order == null) return;
var children = parent.Elements().ToList();
if (children.Count <= 1) return;
var orderIndex = new Dictionary<string, int>();
for (int i = 0; i < order.Length; i++)
orderIndex[order[i]] = i;
int unknownBase = order.Length;
int unknownCounter = 0;
var sorted = children
.Select(c => (Element: c, QName: GetQualifiedName(c)))
.OrderBy(x => orderIndex.TryGetValue(x.QName, out var idx) ? idx : unknownBase + unknownCounter++)
.Select(x => x.Element)
.ToList();
parent.RemoveNodes();
foreach (var child in sorted)
parent.Add(child);
}
private static string GetQualifiedName(XElement element)
{
var ns = element.Name.Namespace;
var local = element.Name.LocalName;
if (ns == Ns.W) return $"w:{local}";
if (ns == Ns.R) return $"r:{local}";
if (ns == Ns.MC) return $"mc:{local}";
return local;
}
}
@@ -0,0 +1,42 @@
using System.Xml.Linq;
namespace MiniMaxAIDocx.Core.OpenXml;
/// <summary>
/// All OpenXML namespace URIs and common content/relationship type constants.
/// </summary>
public static class Ns
{
public static readonly XNamespace W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
public static readonly XNamespace R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
public static readonly XNamespace WP = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
public static readonly XNamespace A = "http://schemas.openxmlformats.org/drawingml/2006/main";
public static readonly XNamespace MC = "http://schemas.openxmlformats.org/markup-compatibility/2006";
public static readonly XNamespace PIC = "http://schemas.openxmlformats.org/drawingml/2006/picture";
public static readonly XNamespace W14 = "http://schemas.microsoft.com/office/word/2010/wordml";
public static readonly XNamespace W15 = "http://schemas.microsoft.com/office/word/2012/wordml";
public static readonly XNamespace W16CID = "http://schemas.microsoft.com/office/word/2016/wordml/cid";
public static readonly XNamespace W16CEX = "http://schemas.microsoft.com/office/word/2018/wordml/cex";
public static readonly XNamespace WPC = "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas";
public static readonly XNamespace WPS = "http://schemas.microsoft.com/office/word/2010/wordprocessingShape";
// Content types
public const string MainDocumentContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
public const string StylesContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml";
public const string HeaderContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml";
public const string FooterContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml";
public const string CommentsContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml";
// Relationship types
public const string DocumentRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
public const string StylesRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
public const string HeaderRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header";
public const string FooterRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer";
public const string CommentsRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
public const string ImageRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
public const string HyperlinkRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
public const string NumberingRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering";
public const string FontTableRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable";
public const string ThemeRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme";
public const string SettingsRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings";
}
@@ -0,0 +1,81 @@
using System.Xml.Linq;
namespace MiniMaxAIDocx.Core.OpenXml;
/// <summary>
/// Result of a run merge operation.
/// </summary>
public record RunMergeResult(int OriginalRunCount, int MergedRunCount, int SizeReductionBytes);
/// <summary>
/// Merges adjacent w:r elements with identical w:rPr formatting to reduce document size.
/// </summary>
public static class RunMerger
{
/// <summary>
/// Merges adjacent runs with identical formatting in all paragraphs of the document body.
/// </summary>
public static RunMergeResult MergeRuns(XDocument document)
{
var body = document.Root?.Element(Ns.W + "body");
if (body == null) return new(0, 0, 0);
int originalCount = 0;
int removedCount = 0;
foreach (var paragraph in body.Descendants(Ns.W + "p"))
{
var runs = paragraph.Elements(Ns.W + "r").ToList();
originalCount += runs.Count;
for (int i = runs.Count - 1; i > 0; i--)
{
var current = runs[i];
var previous = runs[i - 1];
if (!AreRunPropertiesEqual(previous, current)) continue;
// Merge text content from current into previous
var prevText = GetOrCreateTextElement(previous);
var currText = current.Element(Ns.W + "t");
if (currText != null && prevText != null)
{
prevText.Value += currText.Value;
// Preserve xml:space="preserve" if either has it
if (currText.Attribute(XNamespace.Xml + "space")?.Value == "preserve" ||
prevText.Value.StartsWith(' ') || prevText.Value.EndsWith(' '))
{
prevText.SetAttributeValue(XNamespace.Xml + "space", "preserve");
}
}
current.Remove();
removedCount++;
}
}
return new(originalCount, originalCount - removedCount, 0);
}
private static bool AreRunPropertiesEqual(XElement run1, XElement run2)
{
var rPr1 = run1.Element(Ns.W + "rPr");
var rPr2 = run2.Element(Ns.W + "rPr");
if (rPr1 == null && rPr2 == null) return true;
if (rPr1 == null || rPr2 == null) return false;
return XNode.DeepEquals(rPr1, rPr2);
}
private static XElement? GetOrCreateTextElement(XElement run)
{
var t = run.Element(Ns.W + "t");
if (t == null)
{
t = new XElement(Ns.W + "t");
run.Add(t);
}
return t;
}
}
@@ -0,0 +1,81 @@
using System.Xml.Linq;
namespace MiniMaxAIDocx.Core.OpenXml;
public record StyleInfo(string Id, string? Name, string Type, string? BasedOn, bool IsDefault);
public record StyleReport(
List<StyleInfo> AllStyles,
Dictionary<string, List<string>> InheritanceTree,
string? DefaultParagraphStyle,
string? DefaultCharacterStyle,
int DirectFormattingCount);
/// <summary>
/// Analyzes the style hierarchy of a DOCX document.
/// </summary>
public static class StyleAnalyzer
{
/// <summary>
/// Analyzes styles.xml content and document.xml for direct formatting usage.
/// </summary>
public static StyleReport Analyze(XDocument stylesXml, XDocument documentXml)
{
var styles = ExtractStyles(stylesXml);
var tree = BuildInheritanceTree(styles);
var defaultPara = styles.FirstOrDefault(s => s.Type == "paragraph" && s.IsDefault)?.Id;
var defaultChar = styles.FirstOrDefault(s => s.Type == "character" && s.IsDefault)?.Id;
var directCount = CountDirectFormatting(documentXml);
return new(styles, tree, defaultPara, defaultChar, directCount);
}
private static List<StyleInfo> ExtractStyles(XDocument stylesXml)
{
var result = new List<StyleInfo>();
var root = stylesXml.Root;
if (root == null) return result;
foreach (var style in root.Elements(Ns.W + "style"))
{
var id = style.Attribute(Ns.W + "styleId")?.Value ?? "";
var name = style.Element(Ns.W + "name")?.Attribute(Ns.W + "val")?.Value;
var type = style.Attribute(Ns.W + "type")?.Value ?? "unknown";
var basedOn = style.Element(Ns.W + "basedOn")?.Attribute(Ns.W + "val")?.Value;
var isDefault = style.Attribute(Ns.W + "default")?.Value == "1";
result.Add(new(id, name, type, basedOn, isDefault));
}
return result;
}
private static Dictionary<string, List<string>> BuildInheritanceTree(List<StyleInfo> styles)
{
var tree = new Dictionary<string, List<string>>();
foreach (var style in styles)
{
var parent = style.BasedOn ?? "(root)";
if (!tree.ContainsKey(parent))
tree[parent] = [];
tree[parent].Add(style.Id);
}
return tree;
}
private static int CountDirectFormatting(XDocument documentXml)
{
var body = documentXml.Root?.Element(Ns.W + "body");
if (body == null) return 0;
int count = 0;
// Count inline rPr on runs (direct character formatting)
count += body.Descendants(Ns.W + "r")
.Count(r => r.Element(Ns.W + "rPr") != null);
// Count inline pPr that contain more than just pStyle (direct paragraph formatting)
count += body.Descendants(Ns.W + "p")
.Select(p => p.Element(Ns.W + "pPr"))
.Count(pPr => pPr != null && pPr.Elements().Any(e => e.Name != Ns.W + "pStyle"));
return count;
}
}
@@ -0,0 +1,99 @@
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
namespace MiniMaxAIDocx.Core.OpenXml;
/// <summary>
/// Helpers for Track Changes (revision marks) operations.
/// </summary>
public static class TrackChangesHelper
{
/// <summary>
/// Wraps a run in a w:ins element to propose an insertion.
/// </summary>
public static InsertedRun ProposeInsertion(Run run, string author, DateTime date)
{
var ins = new InsertedRun
{
Author = author,
Date = date,
Id = run.Parent is Body body ? GetNextRevisionId(body).ToString() : "1"
};
run.Remove();
ins.Append(run);
return ins;
}
/// <summary>
/// Wraps a run in a w:del element, converting w:t to w:delText.
/// </summary>
public static DeletedRun ProposeDeletion(Run run, string author, DateTime date)
{
// Convert w:t elements to w:delText
foreach (var text in run.Elements<Text>().ToList())
{
var delText = new DeletedText { Text = text.Text, Space = SpaceProcessingModeValues.Preserve };
text.InsertAfterSelf(delText);
text.Remove();
}
var del = new DeletedRun
{
Author = author,
Date = date,
Id = run.Parent is Body body ? GetNextRevisionId(body).ToString() : "1"
};
run.Remove();
del.Append(run);
return del;
}
/// <summary>
/// Accepts an insertion by removing the w:ins wrapper and keeping content.
/// </summary>
public static void AcceptInsertion(OpenXmlElement insElement)
{
if (insElement is not InsertedRun) return;
var parent = insElement.Parent;
if (parent == null) return;
var children = insElement.ChildElements.ToList();
foreach (var child in children)
{
child.Remove();
insElement.InsertBeforeSelf(child);
}
insElement.Remove();
}
/// <summary>
/// Accepts a deletion by removing the entire w:del element and its content.
/// </summary>
public static void AcceptDeletion(OpenXmlElement delElement)
{
delElement.Remove();
}
/// <summary>
/// Finds the maximum existing revision ID in the document and returns the next one.
/// </summary>
public static int GetNextRevisionId(WordprocessingDocument doc)
{
var body = doc.MainDocumentPart?.Document?.Body;
if (body == null) return 1;
return GetNextRevisionId(body);
}
private static int GetNextRevisionId(OpenXmlElement root)
{
int maxId = 0;
foreach (var element in root.Descendants())
{
var idAttr = element.GetAttributes().FirstOrDefault(a => a.LocalName == "id");
if (idAttr.Value != null && int.TryParse(idAttr.Value, out int id) && id > maxId)
maxId = id;
}
return maxId + 1;
}
}
@@ -0,0 +1,23 @@
namespace MiniMaxAIDocx.Core.OpenXml;
/// <summary>
/// Conversion utilities between OpenXML measurement units (DXA, EMU, points, half-points).
/// </summary>
public static class UnitConverter
{
// 1 inch = 1440 DXA = 914400 EMU = 72 pt = 144 half-pt
public static int InchesToDxa(double inches) => (int)(inches * 1440);
public static int CmToDxa(double cm) => (int)(cm * 567.0);
public static int PtToDxa(double pt) => (int)(pt * 20);
public static long InchesToEmu(double inches) => (long)(inches * 914400);
public static long CmToEmu(double cm) => (long)(cm * 360000);
public static int PtToHalfPt(double pt) => (int)(pt * 2);
public static string FontSizeToSz(double ptSize) => ((int)(ptSize * 2)).ToString();
public static double DxaToInches(int dxa) => dxa / 1440.0;
public static double DxaToCm(int dxa) => dxa / 567.0;
public static double DxaToPt(int dxa) => dxa / 20.0;
public static double EmuToInches(long emu) => emu / 914400.0;
public static double EmuToCm(long emu) => emu / 360000.0;
}
@@ -0,0 +1,910 @@
// ============================================================================
// AestheticRecipeSamples_Batch1.cs — IEEE & ACM conference paper recipes
// ============================================================================
// Two-column academic conference styles faithfully reproducing the typographic
// conventions of IEEEtran.cls and acmart.cls for DOCX output.
//
// UNIT REFERENCE:
// Font size: half-points (20 = 10pt, 18 = 9pt, 16 = 8pt)
// Spacing: DXA = twentieths of a point (1440 DXA = 1 inch)
// Borders: eighth-points (4 = 0.5pt, 8 = 1pt, 12 = 1.5pt)
// Line spacing "line": 240ths of single spacing (240 = 1.0x)
// ============================================================================
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using WpColumns = DocumentFormat.OpenXml.Wordprocessing.Columns;
using WpPageSize = DocumentFormat.OpenXml.Wordprocessing.PageSize;
namespace MiniMaxAIDocx.Core.Samples;
public static partial class AestheticRecipeSamples
{
// ════════════════════════════════════════════════════════════════════════
// RECIPE 6: IEEE CONFERENCE (IEEEtran)
// ════════════════════════════════════════════════════════════════════════
/// <summary>
/// Recipe: IEEE Conference Paper (IEEEtran.cls v1.8b)
/// Source: IEEEtran.cls v1.8b — the standard LaTeX class for IEEE transactions
/// and conference proceedings.
///
/// Feel: Dense, formal, information-rich two-column layout.
/// Best for: IEEE conference submissions, transactions papers, technical reports
/// following IEEE style.
///
/// Design rationale (all values from IEEEtran.cls source):
/// - US Letter, narrow margins (0.625in L/R): maximizes text area for the
/// two-column layout. IEEE papers prioritize information density.
/// - Two columns with 0.25in (360 DXA) gutter: standard IEEE column separation.
/// Narrow gutter is feasible because the small font creates short line lengths.
/// - 10pt Times New Roman body (sz=20): IEEE's standard body size. TNR is the
/// required typeface. 10pt in two columns yields ~40 characters per line —
/// optimal for rapid technical reading.
/// - 24pt title, centered, NOT bold (sz=48): IEEEtran titles are large but
/// use regular weight. The size alone provides hierarchy.
/// - Section headings (H1): 10pt small caps, centered, Roman numeral prefix
/// convention (sz=20). Small caps at body size creates subtle hierarchy
/// without disrupting the dense layout.
/// - Subsection headings (H2): 10pt italic, flush left (sz=20). Italic at
/// body size is the minimal viable distinction from body text.
/// - Single spacing (line=240): mandatory for IEEE camera-ready format.
/// - First-line indent 0.125in (180 DXA): very small indent suits the narrow
/// column width.
/// - 0pt paragraph spacing: IEEE uses no inter-paragraph space; the first-line
/// indent is the sole paragraph separator.
/// - Captions: 8pt (sz=16) — subordinate to body, centered under figures/tables.
/// </summary>
public static void CreateIEEEConferenceDocument(string outputPath)
{
using var doc = WordprocessingDocument.Create(outputPath, WordprocessingDocumentType.Document);
var mainPart = doc.AddMainDocumentPart();
mainPart.Document = new Document(new Body());
var body = mainPart.Document.Body!;
// ── Styles ──
var stylesPart = mainPart.AddNewPart<StyleDefinitionsPart>();
stylesPart.Styles = new Styles();
var styles = stylesPart.Styles;
// DocDefaults: Times New Roman 10pt, single spacing, 0.125in first-line indent
styles.Append(new DocDefaults(
new RunPropertiesDefault(
new RunPropertiesBaseStyle(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman",
EastAsia = "SimSun",
ComplexScript = "Times New Roman"
},
new FontSize { Val = "20" }, // 10pt body (IEEEtran standard)
new FontSizeComplexScript { Val = "20" },
new Color { Val = "000000" }, // Pure black
new Languages { Val = "en-US", EastAsia = "zh-CN" }
)
),
new ParagraphPropertiesDefault(
new ParagraphPropertiesBaseStyle(
new SpacingBetweenLines
{
// Single spacing: mandatory for IEEE camera-ready
Line = "240",
LineRule = LineSpacingRuleValues.Auto,
After = "0",
Before = "0"
},
// First-line indent: 0.125in = 180 DXA (very small, suits narrow columns)
new Indentation { FirstLine = "180" }
)
)
));
// ── Normal style ──
styles.Append(CreateParagraphStyle(
styleId: "Normal",
styleName: "Normal",
isDefault: true,
uiPriority: 0
));
// ── Title style: 24pt centered, NOT bold ──
// IEEEtran.cls \maketitle: \LARGE (24pt at 10pt base), centered, no bold
var titleRPr = new StyleRunProperties(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman",
EastAsia = "SimSun",
ComplexScript = "Times New Roman"
},
new FontSize { Val = "48" }, // 24pt
new FontSizeComplexScript { Val = "48" },
new Color { Val = "000000" }
// No Bold — IEEEtran titles are NOT bold
);
styles.Append(new Style(
new StyleName { Val = "Title" },
new BasedOn { Val = "Normal" },
new NextParagraphStyle { Val = "Normal" },
new UIPriority { Val = 10 },
new PrimaryStyle(),
new StyleParagraphProperties(
new Justification { Val = JustificationValues.Center },
new SpacingBetweenLines { Before = "0", After = "240" },
new Indentation { FirstLine = "0" } // No indent for title
),
titleRPr
)
{
Type = StyleValues.Paragraph,
StyleId = "Title",
Default = false
});
// ── Heading 1: 10pt small caps, centered ──
// IEEEtran \section: \centering\scshape at body size, Roman numeral prefix
var h1RPr = new StyleRunProperties(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman",
EastAsia = "SimSun",
ComplexScript = "Times New Roman"
},
new FontSize { Val = "20" }, // 10pt — same as body
new FontSizeComplexScript { Val = "20" },
new Color { Val = "000000" },
new SmallCaps() // Small caps for section headings
);
styles.Append(new Style(
new StyleName { Val = "heading 1" },
new BasedOn { Val = "Normal" },
new NextParagraphStyle { Val = "Normal" },
new UIPriority { Val = 9 },
new PrimaryStyle(),
new StyleParagraphProperties(
new KeepNext(),
new KeepLines(),
new Justification { Val = JustificationValues.Center },
new SpacingBetweenLines { Before = "240", After = "120" },
new Indentation { FirstLine = "0" },
new OutlineLevel { Val = 0 }
),
h1RPr
)
{
Type = StyleValues.Paragraph,
StyleId = "Heading1",
Default = false
});
// ── Heading 2: 10pt italic, flush left ──
// IEEEtran \subsection: \itshape at body size, flush left
var h2RPr = new StyleRunProperties(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman",
EastAsia = "SimSun",
ComplexScript = "Times New Roman"
},
new FontSize { Val = "20" }, // 10pt — same as body
new FontSizeComplexScript { Val = "20" },
new Color { Val = "000000" },
new Italic() // Italic for subsection headings
);
styles.Append(new Style(
new StyleName { Val = "heading 2" },
new BasedOn { Val = "Normal" },
new NextParagraphStyle { Val = "Normal" },
new UIPriority { Val = 9 },
new PrimaryStyle(),
new StyleParagraphProperties(
new KeepNext(),
new KeepLines(),
new SpacingBetweenLines { Before = "180", After = "60" },
new Indentation { FirstLine = "0" },
new OutlineLevel { Val = 1 }
),
h2RPr
)
{
Type = StyleValues.Paragraph,
StyleId = "Heading2",
Default = false
});
// ── Abstract style: 9pt bold "Abstract" label convention ──
styles.Append(CreateParagraphStyle(
styleId: "Abstract",
styleName: "Abstract",
basedOn: "Normal",
uiPriority: 11
));
// ── Caption style: 8pt (sz=16) ──
styles.Append(CreateCaptionStyle(
fontSizeHalfPts: "16", // 8pt — IEEE standard caption size
color: "000000",
italic: false // IEEE captions are not italic
));
// ── Page setup: US Letter, IEEE margins, two-column ──
// IEEEtran.cls: top=0.75in, bottom=1in, left=right=0.625in
var sectPr = new SectionProperties(
new WpPageSize { Width = 12240U, Height = 15840U }, // US Letter
new PageMargin
{
Top = 1080, // 0.75in
Bottom = 1440, // 1in
Left = 900U, // 0.625in
Right = 900U, // 0.625in
Header = 720U, Footer = 720U, Gutter = 0U
},
// Two-column layout: 0.25in gutter = 360 DXA
new WpColumns { ColumnCount = 2, Space = "360" }
);
// ── Page numbers: bottom center, 8pt ──
AddPageNumberFooter(mainPart, sectPr,
alignment: JustificationValues.Center,
fontSizeHalfPts: "16", // 8pt
color: "000000",
format: PageNumberFormat.Plain
);
// ── Sample content: IEEE paper structure ──
// Title (spans both columns via the Title style)
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Title" }
),
new Run(new Text("Deep Learning Approaches for Automated Document Layout Analysis"))
));
// Author line (centered, no indent)
body.Append(new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center },
new SpacingBetweenLines { After = "120" },
new Indentation { FirstLine = "0" }
),
new Run(
new RunProperties(new FontSize { Val = "20" }, new FontSizeComplexScript { Val = "20" }),
new Text("Jane A. Smith, John B. Doe, and Alice C. Johnson")
)
));
// Affiliation (centered, italic, smaller)
body.Append(new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center },
new SpacingBetweenLines { After = "240" },
new Indentation { FirstLine = "0" }
),
new Run(
new RunProperties(
new FontSize { Val = "18" }, new FontSizeComplexScript { Val = "18" },
new Italic()
),
new Text("Department of Computer Science, Example University, City, Country")
)
));
// Abstract
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Abstract" },
new Indentation { FirstLine = "0" },
new SpacingBetweenLines { After = "120" }
),
new Run(
new RunProperties(new Bold(), new Italic(), new FontSize { Val = "18" }, new FontSizeComplexScript { Val = "18" }),
new Text("Abstract") { Space = SpaceProcessingModeValues.Preserve }
),
new Run(
new RunProperties(new FontSize { Val = "18" }, new FontSizeComplexScript { Val = "18" }),
new Text("\u2014This paper presents a comprehensive framework for automated document "
+ "layout analysis using deep learning. We propose a novel architecture that "
+ "combines convolutional neural networks with transformer-based attention "
+ "mechanisms to accurately segment and classify document regions. Experimental "
+ "results on benchmark datasets demonstrate state-of-the-art performance.")
{ Space = SpaceProcessingModeValues.Preserve }
)
));
// I. INTRODUCTION (Roman numeral convention rendered in text)
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Heading1" }
),
new Run(new Text("I. Introduction"))
));
AddSampleParagraph(body, "Document layout analysis is a fundamental step in document "
+ "understanding pipelines. The ability to automatically identify and classify "
+ "regions within a document image has applications in digitization, information "
+ "extraction, and accessibility.", "Normal");
AddSampleParagraph(body, "Recent advances in deep learning have significantly improved "
+ "the accuracy of layout analysis systems. However, challenges remain in handling "
+ "complex multi-column layouts and heterogeneous document types.", "Normal");
// II. RELATED WORK
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Heading1" }
),
new Run(new Text("II. Related Work"))
));
// A. Subsection
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Heading2" }
),
new Run(new Text("A. Traditional Methods"))
));
AddSampleParagraph(body, "Early approaches to document layout analysis relied on "
+ "rule-based methods and connected component analysis. These methods perform well "
+ "on structured documents but struggle with complex layouts.", "Normal");
// B. Subsection
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Heading2" }
),
new Run(new Text("B. Deep Learning Methods"))
));
AddSampleParagraph(body, "Convolutional neural networks have been successfully applied "
+ "to document layout analysis, achieving significant improvements over traditional "
+ "methods on standard benchmarks.", "Normal");
// III. PROPOSED METHOD
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Heading1" }
),
new Run(new Text("III. Proposed Method"))
));
AddSampleParagraph(body, "Our proposed framework integrates a feature pyramid network "
+ "backbone with a transformer decoder module. The architecture processes document "
+ "images at multiple scales to capture both fine-grained character-level features "
+ "and coarse layout structures.", "Normal");
// Table
body.Append(CreateThreeLineTable(
new[] { "Method", "Precision", "Recall", "F1" },
new[]
{
new[] { "Rule-based", "0.823", "0.791", "0.807" },
new[] { "CNN-only", "0.912", "0.887", "0.899" },
new[] { "Ours", "0.956", "0.943", "0.949" }
}
));
AddSampleParagraph(body, "TABLE I: Comparison of layout analysis methods on PubLayNet.", "Caption");
// IV. CONCLUSION
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Heading1" }
),
new Run(new Text("IV. Conclusion"))
));
AddSampleParagraph(body, "We have presented a novel deep learning framework for document "
+ "layout analysis that achieves state-of-the-art results. Future work will explore "
+ "extending the approach to handle more diverse document types.", "Normal");
// Section properties must be last child of body
body.Append(sectPr);
}
// ════════════════════════════════════════════════════════════════════════
// RECIPE 7: ACM CONFERENCE (acmart)
// ════════════════════════════════════════════════════════════════════════
/// <summary>
/// Recipe: ACM Conference Paper (acmart.cls v2.x, ACM Author Guide)
/// Source: acmart.cls v2.x — the consolidated ACM master article template,
/// and the ACM Author Guide for typographic specifications.
///
/// Feel: Clean, structured, slightly more open than IEEE.
/// Best for: ACM conference proceedings (SIGCHI, SIGMOD, SIGGRAPH, etc.),
/// ACM journal submissions.
///
/// Design rationale (all values from acmart.cls and ACM Author Guide):
/// - US Letter, 1.25in top/bottom, 0.75in L/R: more generous vertical margins
/// than IEEE, giving a less cramped appearance.
/// - Two columns with 0.33in (480 DXA) gutter: slightly wider than IEEE's
/// 0.25in, providing better visual separation between columns.
/// - 9pt Times New Roman body (sz=18): ACM's standard body size. The original
/// acmart uses Linux Libertine, but TNR is the accessible fallback specified
/// in the ACM Author Guide for systems without Libertine.
/// - 14.4pt bold title, flush left (sz=29): ACM titles are bold and left-aligned,
/// unlike IEEE's centered unbolded titles. The 14.4pt size (1.6x body) creates
/// strong but not overwhelming hierarchy.
/// - H1: 10pt bold ALL CAPS, flush left, arabic numbered (sz=20). ALL CAPS at
/// body size with bold creates definitive section breaks.
/// - H2: 10pt bold title case, flush left (sz=20). Bold without caps is the
/// minimal step down from H1.
/// - H3: 10pt bold italic, flush left (sz=20). Adding italic distinguishes
/// from H2 while maintaining the same weight.
/// - Single spacing: required for ACM camera-ready format.
/// - First-line indent ~10pt (200 DXA): slightly larger than IEEE's 0.125in,
/// matching ACM's convention of a roughly 1em indent at 9pt.
/// - Captions: 8pt (sz=16) — consistent with ACM figure/table caption style.
/// - References: 7.5pt (sz=15) — ACM uses a smaller font for the bibliography
/// to maximize space for content.
/// </summary>
public static void CreateACMConferenceDocument(string outputPath)
{
using var doc = WordprocessingDocument.Create(outputPath, WordprocessingDocumentType.Document);
var mainPart = doc.AddMainDocumentPart();
mainPart.Document = new Document(new Body());
var body = mainPart.Document.Body!;
// ── Styles ──
var stylesPart = mainPart.AddNewPart<StyleDefinitionsPart>();
stylesPart.Styles = new Styles();
var styles = stylesPart.Styles;
// DocDefaults: Times New Roman 9pt (TNR as Libertine fallback), single spacing
styles.Append(new DocDefaults(
new RunPropertiesDefault(
new RunPropertiesBaseStyle(
new RunFonts
{
// ACM specifies Linux Libertine; TNR is the accessible fallback
// per ACM Author Guide for systems without Libertine installed
Ascii = "Times New Roman",
HighAnsi = "Times New Roman",
EastAsia = "SimSun",
ComplexScript = "Times New Roman"
},
new FontSize { Val = "18" }, // 9pt body (acmart standard)
new FontSizeComplexScript { Val = "18" },
new Color { Val = "000000" }, // Pure black
new Languages { Val = "en-US", EastAsia = "zh-CN" }
)
),
new ParagraphPropertiesDefault(
new ParagraphPropertiesBaseStyle(
new SpacingBetweenLines
{
// Single spacing: ACM camera-ready requirement
Line = "240",
LineRule = LineSpacingRuleValues.Auto,
After = "0",
Before = "0"
},
// First-line indent: ~10pt = 200 DXA (roughly 1em at 9pt)
new Indentation { FirstLine = "200" }
)
)
));
// ── Normal style ──
styles.Append(CreateParagraphStyle(
styleId: "Normal",
styleName: "Normal",
isDefault: true,
uiPriority: 0
));
// ── Title style: 14.4pt bold, flush left ──
// acmart \maketitle: \LARGE\bfseries, left-aligned
var titleRPr = new StyleRunProperties(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman",
EastAsia = "SimSun",
ComplexScript = "Times New Roman"
},
new FontSize { Val = "29" }, // 14.4pt (≈29 half-points)
new FontSizeComplexScript { Val = "29" },
new Color { Val = "000000" },
new Bold() // ACM titles ARE bold
);
styles.Append(new Style(
new StyleName { Val = "Title" },
new BasedOn { Val = "Normal" },
new NextParagraphStyle { Val = "Normal" },
new UIPriority { Val = 10 },
new PrimaryStyle(),
new StyleParagraphProperties(
// Flush left — ACM titles are NOT centered
new SpacingBetweenLines { Before = "0", After = "200" },
new Indentation { FirstLine = "0" }
),
titleRPr
)
{
Type = StyleValues.Paragraph,
StyleId = "Title",
Default = false
});
// ── Heading 1: 10pt bold ALL CAPS, flush left ──
// acmart \section: \bfseries at body size, uppercase
var h1RPr = new StyleRunProperties(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman",
EastAsia = "SimSun",
ComplexScript = "Times New Roman"
},
new FontSize { Val = "20" }, // 10pt
new FontSizeComplexScript { Val = "20" },
new Color { Val = "000000" },
new Bold(),
new Caps() // ALL CAPS for H1
);
styles.Append(new Style(
new StyleName { Val = "heading 1" },
new BasedOn { Val = "Normal" },
new NextParagraphStyle { Val = "Normal" },
new UIPriority { Val = 9 },
new PrimaryStyle(),
new StyleParagraphProperties(
new KeepNext(),
new KeepLines(),
new SpacingBetweenLines { Before = "240", After = "120" },
new Indentation { FirstLine = "0" },
new OutlineLevel { Val = 0 }
),
h1RPr
)
{
Type = StyleValues.Paragraph,
StyleId = "Heading1",
Default = false
});
// ── Heading 2: 10pt bold title case, flush left ──
// acmart \subsection: \bfseries, no case change
var h2RPr = new StyleRunProperties(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman",
EastAsia = "SimSun",
ComplexScript = "Times New Roman"
},
new FontSize { Val = "20" }, // 10pt
new FontSizeComplexScript { Val = "20" },
new Color { Val = "000000" },
new Bold() // Bold, no caps
);
styles.Append(new Style(
new StyleName { Val = "heading 2" },
new BasedOn { Val = "Normal" },
new NextParagraphStyle { Val = "Normal" },
new UIPriority { Val = 9 },
new PrimaryStyle(),
new StyleParagraphProperties(
new KeepNext(),
new KeepLines(),
new SpacingBetweenLines { Before = "200", After = "80" },
new Indentation { FirstLine = "0" },
new OutlineLevel { Val = 1 }
),
h2RPr
)
{
Type = StyleValues.Paragraph,
StyleId = "Heading2",
Default = false
});
// ── Heading 3: 10pt bold italic, flush left ──
// acmart \subsubsection: \bfseries\itshape
var h3RPr = new StyleRunProperties(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman",
EastAsia = "SimSun",
ComplexScript = "Times New Roman"
},
new FontSize { Val = "20" }, // 10pt
new FontSizeComplexScript { Val = "20" },
new Color { Val = "000000" },
new Bold(),
new Italic() // Bold italic for H3
);
styles.Append(new Style(
new StyleName { Val = "heading 3" },
new BasedOn { Val = "Normal" },
new NextParagraphStyle { Val = "Normal" },
new UIPriority { Val = 9 },
new PrimaryStyle(),
new StyleParagraphProperties(
new KeepNext(),
new KeepLines(),
new SpacingBetweenLines { Before = "160", After = "60" },
new Indentation { FirstLine = "0" },
new OutlineLevel { Val = 2 }
),
h3RPr
)
{
Type = StyleValues.Paragraph,
StyleId = "Heading3",
Default = false
});
// ── Caption style: 8pt (sz=16) ──
styles.Append(CreateCaptionStyle(
fontSizeHalfPts: "16", // 8pt — ACM standard caption size
color: "000000",
italic: false
));
// ── References style: 7.5pt (sz=15) ──
var refsRPr = new StyleRunProperties(
new FontSize { Val = "15" }, // 7.5pt
new FontSizeComplexScript { Val = "15" }
);
styles.Append(new Style(
new StyleName { Val = "References" },
new BasedOn { Val = "Normal" },
new UIPriority { Val = 37 },
new PrimaryStyle(),
new StyleParagraphProperties(
new SpacingBetweenLines { After = "40" },
new Indentation { FirstLine = "0", Left = "360", Hanging = "360" }
),
refsRPr
)
{
Type = StyleValues.Paragraph,
StyleId = "References",
Default = false
});
// ── Page setup: US Letter, ACM margins, two-column ──
// acmart.cls: top=1.25in, bottom=1.25in, left=right=0.75in
var sectPr = new SectionProperties(
new WpPageSize { Width = 12240U, Height = 15840U }, // US Letter
new PageMargin
{
Top = 1800, // 1.25in
Bottom = 1800, // 1.25in
Left = 1080U, // 0.75in
Right = 1080U, // 0.75in
Header = 720U, Footer = 720U, Gutter = 0U
},
// Two-column layout: 0.33in gutter = 480 DXA
new WpColumns { ColumnCount = 2, Space = "480" }
);
// ── Page numbers: bottom center, 8pt ──
AddPageNumberFooter(mainPart, sectPr,
alignment: JustificationValues.Center,
fontSizeHalfPts: "16", // 8pt
color: "000000",
format: PageNumberFormat.Plain
);
// ── Sample content: ACM paper structure ──
// Title (flush left, bold)
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Title" }
),
new Run(new Text("Towards Scalable Graph Neural Networks for Heterogeneous Document Understanding"))
));
// Author block (flush left)
body.Append(new Paragraph(
new ParagraphProperties(
new SpacingBetweenLines { After = "60" },
new Indentation { FirstLine = "0" }
),
new Run(
new RunProperties(new FontSize { Val = "18" }, new FontSizeComplexScript { Val = "18" }),
new Text("Maria R. Garcia")
)
));
body.Append(new Paragraph(
new ParagraphProperties(
new SpacingBetweenLines { After = "60" },
new Indentation { FirstLine = "0" }
),
new Run(
new RunProperties(
new FontSize { Val = "16" }, new FontSizeComplexScript { Val = "16" },
new Italic()
),
new Text("Example University, City, Country")
)
));
body.Append(new Paragraph(
new ParagraphProperties(
new SpacingBetweenLines { After = "200" },
new Indentation { FirstLine = "0" }
),
new Run(
new RunProperties(
new FontSize { Val = "16" }, new FontSizeComplexScript { Val = "16" }
),
new Text("garcia@example.edu")
)
));
// Abstract section
body.Append(new Paragraph(
new ParagraphProperties(
new Indentation { FirstLine = "0" },
new SpacingBetweenLines { After = "80" }
),
new Run(
new RunProperties(
new Bold(),
new FontSize { Val = "18" }, new FontSizeComplexScript { Val = "18" }
),
new Text("ABSTRACT")
)
));
AddSampleParagraph(body, "Graph neural networks (GNNs) have emerged as a powerful tool for "
+ "document understanding tasks that require modeling relationships between document "
+ "elements. We present a scalable GNN architecture that processes heterogeneous "
+ "document graphs containing text, table, and figure nodes. Our approach achieves "
+ "competitive results while reducing computational costs by 40%.", "Normal");
// CCS Concepts / Keywords (ACM-specific metadata)
body.Append(new Paragraph(
new ParagraphProperties(
new Indentation { FirstLine = "0" },
new SpacingBetweenLines { Before = "120", After = "120" }
),
new Run(
new RunProperties(
new Bold(),
new FontSize { Val = "16" }, new FontSizeComplexScript { Val = "16" }
),
new Text("Keywords: ") { Space = SpaceProcessingModeValues.Preserve }
),
new Run(
new RunProperties(
new FontSize { Val = "16" }, new FontSizeComplexScript { Val = "16" }
),
new Text("graph neural networks, document understanding, scalability")
)
));
// 1 INTRODUCTION (arabic numbered, ALL CAPS via style)
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Heading1" }
),
new Run(new Text("1 Introduction"))
));
AddSampleParagraph(body, "Document understanding encompasses a broad set of tasks including "
+ "layout analysis, information extraction, and document classification. Recent work "
+ "has demonstrated that modeling the structural relationships between document "
+ "elements can significantly improve performance on these tasks.", "Normal");
AddSampleParagraph(body, "Graph neural networks provide a natural framework for representing "
+ "and reasoning about document structure. However, existing GNN-based approaches face "
+ "scalability challenges when processing large or complex documents.", "Normal");
// 2 RELATED WORK
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Heading1" }
),
new Run(new Text("2 Related Work"))
));
// 2.1 Subsection
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Heading2" }
),
new Run(new Text("2.1 Document Representation Learning"))
));
AddSampleParagraph(body, "Pre-trained language models have been adapted for document "
+ "understanding by incorporating layout information. LayoutLM and its successors "
+ "demonstrate the value of multi-modal pre-training for document tasks.", "Normal");
// 2.1.1 Sub-subsection
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Heading3" }
),
new Run(new Text("2.1.1 Multi-Modal Approaches"))
));
AddSampleParagraph(body, "Multi-modal approaches jointly model text, layout, and visual "
+ "features. This integration has proven critical for tasks where visual appearance "
+ "carries semantic meaning, such as form understanding.", "Normal");
// 3 METHOD
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Heading1" }
),
new Run(new Text("3 Proposed Method"))
));
AddSampleParagraph(body, "We propose HetDocGNN, a heterogeneous graph neural network "
+ "designed specifically for document understanding. The architecture operates on "
+ "a document graph where nodes represent text blocks, tables, and figures, and "
+ "edges encode spatial and logical relationships.", "Normal");
// Results table
body.Append(CreateThreeLineTable(
new[] { "Model", "DocVQA", "InfoVQA", "Params" },
new[]
{
new[] { "LayoutLMv3", "83.4", "45.1", "133M" },
new[] { "UDOP", "84.7", "47.4", "770M" },
new[] { "HetDocGNN", "85.2", "48.9", "89M" }
}
));
AddSampleParagraph(body, "Table 1: Comparison on document understanding benchmarks.", "Caption");
// 4 CONCLUSION
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Heading1" }
),
new Run(new Text("4 Conclusion"))
));
AddSampleParagraph(body, "We have presented HetDocGNN, a scalable graph neural network "
+ "for heterogeneous document understanding. Our approach achieves state-of-the-art "
+ "results with significantly fewer parameters than competing methods.", "Normal");
// REFERENCES section
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Heading1" }
),
new Run(new Text("References"))
));
// Sample references in ACM style (7.5pt)
AddSampleParagraph(body, "[1] Yiheng Xu, et al. 2020. LayoutLM: Pre-training of Text and "
+ "Layout for Document Image Understanding. In KDD '20. ACM, 1192\u20131200.", "References");
AddSampleParagraph(body, "[2] Zhiliang Peng, et al. 2023. UDOP: Unifying Vision, Text, "
+ "and Layout for Universal Document Processing. In CVPR '23. 19254\u201319264.", "References");
AddSampleParagraph(body, "[3] Zilong Wang, et al. 2022. DocFormer: End-to-End Transformer "
+ "for Document Understanding. In ICCV '22. 993\u20131003.", "References");
// Section properties must be last child of body
body.Append(sectPr);
}
}
@@ -0,0 +1,999 @@
// ============================================================================
// AestheticRecipeSamples_Batch2.cs — Academic citation style recipes (APA 7, MLA 9)
// ============================================================================
// Recipes 8-9: Strict compliance with academic citation style guides.
// These are NOT aesthetic "design" choices — they are codified standards
// mandated by publishers, universities, and professional organizations.
//
// UNIT REFERENCE:
// Font size: half-points (22 = 11pt, 24 = 12pt, 32 = 16pt)
// Spacing: DXA = twentieths of a point (1440 DXA = 1 inch)
// Borders: eighth-points (4 = 0.5pt, 8 = 1pt, 12 = 1.5pt)
// Line spacing "line": 240ths of single spacing (240 = 1.0x, 480 = 2.0x)
// ============================================================================
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using WpPageSize = DocumentFormat.OpenXml.Wordprocessing.PageSize;
namespace MiniMaxAIDocx.Core.Samples;
public static partial class AestheticRecipeSamples
{
// ════════════════════════════════════════════════════════════════════════
// RECIPE 8: APA 7TH EDITION (PROFESSIONAL PAPER)
// ════════════════════════════════════════════════════════════════════════
/// <summary>
/// Recipe: APA 7th Edition — Professional Paper
/// Source: Publication Manual of the American Psychological Association,
/// 7th edition (2020), Chapters 2 (Paper Elements) and 6 (Mechanics of Style).
///
/// Key APA 7 specifications:
/// - Font: 12pt Times New Roman (Section 2.19). Also acceptable: 11pt Calibri,
/// 11pt Arial, 10pt Lucida Sans Unicode, or 11pt Georgia.
/// - Margins: 1 inch on all sides (Section 2.22).
/// - Line spacing: Double-spaced throughout, including title page and references (Section 2.21).
/// - Paragraph indent: 0.5 inch first-line indent for body paragraphs (Section 2.24).
/// - Heading levels (Section 2.27):
/// Level 1: Centered, Bold, Title Case Heading
/// Level 2: Flush Left, Bold, Title Case Heading
/// Level 3: Flush Left, Bold Italic, Title Case Heading
/// Level 4: Indented, Bold, Title Case Heading, Ending With a Period. (run-in)
/// Level 5: Indented, Bold Italic, Title Case Heading, Ending With a Period. (run-in)
/// All headings are 12pt — hierarchy through format, NOT size.
/// - Page numbers: top right corner on every page including title page (Section 2.18).
/// - Running head: flush left, ALL CAPS, for professional papers only (Section 2.18).
/// - Abstract: "Abstract" centered bold; single paragraph, not indented (Section 2.9).
/// - No numbered headings (APA does not use section numbers).
///
/// Design rationale:
/// - Every parameter is dictated by the style guide, not aesthetic preference.
/// - Double spacing with first-line indent (no paragraph spacing) is the
/// traditional academic convention — it provides annotation room and
/// clear paragraph boundaries without wasting vertical space.
/// - Uniform 12pt headings ensure the text content is primary; headings
/// serve as navigational aids, not visual statements.
/// </summary>
public static void CreateAPA7Document(string outputPath)
{
using var doc = WordprocessingDocument.Create(outputPath, WordprocessingDocumentType.Document);
var mainPart = doc.AddMainDocumentPart();
mainPart.Document = new Document(new Body());
var body = mainPart.Document.Body!;
// ── Styles ──
var stylesPart = mainPart.AddNewPart<StyleDefinitionsPart>();
stylesPart.Styles = new Styles();
var styles = stylesPart.Styles;
// DocDefaults: 12pt Times New Roman, double spacing, 0.5in first-line indent
// NOTE: 11pt Calibri and 11pt Arial are also acceptable per APA 7 Section 2.19
styles.Append(new DocDefaults(
new RunPropertiesDefault(
new RunPropertiesBaseStyle(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman",
EastAsia = "SimSun",
ComplexScript = "Times New Roman"
},
new FontSize { Val = "24" }, // 12pt (half-points)
new FontSizeComplexScript { Val = "24" },
new Color { Val = "000000" }, // Pure black
new Languages { Val = "en-US", EastAsia = "zh-CN" }
)
),
new ParagraphPropertiesDefault(
new ParagraphPropertiesBaseStyle(
new SpacingBetweenLines
{
// Double spacing throughout (APA 7, Section 2.21)
// 480 = 2.0x (240 = single spacing)
Line = "480",
LineRule = LineSpacingRuleValues.Auto,
After = "0" // No paragraph spacing — APA uses indent, not space
},
// First-line indent 0.5in = 720 DXA (APA 7, Section 2.24)
new Indentation { FirstLine = "720" }
)
)
));
// ── Normal style ──
styles.Append(CreateParagraphStyle(
styleId: "Normal",
styleName: "Normal",
isDefault: true,
uiPriority: 0
));
// ── APA Level 1: Centered, Bold, Title Case ──
// Same 12pt as body — hierarchy via format, NOT size (APA 7, Section 2.27)
styles.Append(CreateAcademicHeadingStyle(
level: 1,
sizeHalfPts: "24", // 12pt — same as body
bold: true,
italic: false,
centered: true,
spaceBefore: "480", // One double-spaced blank line before
spaceAfter: "0"
));
// ── APA Level 2: Flush Left, Bold, Title Case ──
styles.Append(CreateAcademicHeadingStyle(
level: 2,
sizeHalfPts: "24", // 12pt — same as body
bold: true,
italic: false,
centered: false,
spaceBefore: "480",
spaceAfter: "0"
));
// ── APA Level 3: Flush Left, Bold Italic, Title Case ──
styles.Append(CreateAcademicHeadingStyle(
level: 3,
sizeHalfPts: "24", // 12pt — same as body
bold: true,
italic: true,
centered: false,
spaceBefore: "480",
spaceAfter: "0"
));
// ── APA Level 4: Indented 0.5in, Bold, Title Case, Ending With Period. ──
// This is a "run-in" heading in APA — the heading text runs into the paragraph.
// In OpenXML we approximate by creating an indented bold paragraph.
styles.Append(CreateAPA7RunInHeadingStyle(
level: 4,
bold: true,
italic: false
));
// ── APA Level 5: Indented 0.5in, Bold Italic, Title Case, Ending With Period. ──
styles.Append(CreateAPA7RunInHeadingStyle(
level: 5,
bold: true,
italic: true
));
// ── "Abstract" label style: centered, bold, no indent ──
styles.Append(CreateAPA7NoIndentCenteredStyle(
styleId: "APAAbstractLabel",
styleName: "APA Abstract Label",
bold: true
));
// ── Abstract body style: no first-line indent ──
styles.Append(CreateAPA7NoIndentStyle(
styleId: "APAAbstractBody",
styleName: "APA Abstract Body"
));
// ── Title page style: centered, bold, no indent ──
styles.Append(CreateAPA7NoIndentCenteredStyle(
styleId: "APATitlePageTitle",
styleName: "APA Title Page Title",
bold: true
));
// ── Title page author/affiliation: centered, no indent, not bold ──
styles.Append(CreateAPA7NoIndentCenteredStyle(
styleId: "APATitlePageInfo",
styleName: "APA Title Page Info",
bold: false
));
// ── Page setup: US Letter, 1in all sides (APA 7, Section 2.22) ──
var sectPr = new SectionProperties(
new WpPageSize { Width = 12240U, Height = 15840U }, // 8.5" x 11"
new PageMargin
{
Top = 1440, Bottom = 1440,
Left = 1440U, Right = 1440U,
Header = 720U, Footer = 720U, Gutter = 0U
}
);
// ── Running head + page number in header ──
// Professional papers: running head flush left (ALL CAPS), page number flush right
// Both in the same header (APA 7, Section 2.18)
AddAPA7Header(mainPart, sectPr, "COGNITIVE EFFECTS OF SLEEP DEPRIVATION");
// ══════════════════════════════════════════════════════════════════
// SAMPLE CONTENT: Title Page, Abstract, Body with all 5 heading levels
// ══════════════════════════════════════════════════════════════════
// ── Title page ──
// Title: centered, bold, upper half of page (3-4 blank lines before)
AddAPA7TitlePage(body,
title: "Cognitive Effects of Sleep Deprivation on Working Memory Performance",
authorName: "Sarah J. Mitchell",
affiliation: "Department of Psychology, University of Washington",
courseLine: "PSY 401: Advanced Cognitive Psychology",
instructorLine: "Dr. Robert Chen",
dateLine: "October 15, 2024"
);
// ── Abstract page ──
AddSampleParagraph(body, "Abstract", "APAAbstractLabel");
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "APAAbstractBody" }
),
new Run(new Text(
"This study examined the effects of acute sleep deprivation on working memory "
+ "performance in college-aged adults. Participants (N = 48) were randomly assigned "
+ "to either a sleep deprivation condition (24 hours without sleep) or a control "
+ "condition (normal sleep). Working memory was assessed using a dual n-back task. "
+ "Results indicated that sleep-deprived participants showed significantly lower "
+ "accuracy (M = 72.3%, SD = 8.1) compared to controls (M = 89.7%, SD = 5.4), "
+ "t(46) = 9.12, p < .001, d = 2.52. These findings suggest that even a single "
+ "night of sleep deprivation substantially impairs working memory capacity."
))
));
// ── Body: Level 1 heading ──
AddSampleParagraph(body, "Cognitive Effects of Sleep Deprivation on Working Memory Performance", "Heading1");
AddSampleParagraph(body,
"Sleep deprivation is increasingly prevalent among college students, with approximately "
+ "50% reporting insufficient sleep on a regular basis (Hershner & Chervin, 2014). The "
+ "consequences of inadequate sleep extend beyond daytime drowsiness, affecting core "
+ "cognitive processes including attention, executive function, and working memory.",
"Normal");
// ── Level 2 heading ──
AddSampleParagraph(body, "Theoretical Framework", "Heading2");
AddSampleParagraph(body,
"Working memory, as conceptualized by Baddeley and Hitch (1974), comprises a central "
+ "executive system supported by the phonological loop and visuospatial sketchpad. Sleep "
+ "deprivation has been hypothesized to primarily affect the central executive component, "
+ "which governs attentional control and task coordination.",
"Normal");
// ── Level 3 heading ──
AddSampleParagraph(body, "Neural Mechanisms of Sleep-Related Cognitive Decline", "Heading3");
AddSampleParagraph(body,
"Neuroimaging studies have demonstrated that sleep deprivation is associated with "
+ "reduced activation in the prefrontal cortex, the neural substrate most closely linked "
+ "to working memory function (Chee & Chuah, 2007). Additionally, thalamic deactivation "
+ "may impair the relay of sensory information necessary for memory encoding.",
"Normal");
// ── Level 4 heading (run-in, bold, ends with period) ──
// APA Level 4 is a run-in heading: the heading text and paragraph text
// share the same line. We approximate with a bold indented paragraph.
body.Append(CreateAPA7RunInParagraph(
headingText: "Prefrontal Cortex Involvement.",
bodyText: " The dorsolateral prefrontal cortex (DLPFC) shows the greatest "
+ "susceptibility to sleep loss. Functional MRI studies reveal a dose-dependent "
+ "relationship between hours of wakefulness and DLPFC activation levels during "
+ "working memory tasks.",
bold: true,
italic: false
));
// ── Level 5 heading (run-in, bold italic, ends with period) ──
body.Append(CreateAPA7RunInParagraph(
headingText: "Glutamatergic Pathways.",
bodyText: " Recent research has identified glutamatergic signaling in the "
+ "prefrontal cortex as a key mediator of sleep deprivation effects on working "
+ "memory. Antagonism of NMDA receptors produces cognitive deficits similar to "
+ "those observed following 24 hours of sleep loss.",
bold: true,
italic: true
));
// ── Level 2: Method section ──
AddSampleParagraph(body, "Method", "Heading2");
AddSampleParagraph(body,
"This experiment used a between-subjects design with sleep condition (deprived vs. "
+ "control) as the independent variable and working memory accuracy as the dependent "
+ "variable. All procedures were approved by the University of Washington Institutional "
+ "Review Board (Protocol #2024-0847).",
"Normal");
// ── Level 2: Results ──
AddSampleParagraph(body, "Results", "Heading2");
AddSampleParagraph(body,
"An independent-samples t test revealed a statistically significant difference in "
+ "working memory accuracy between the sleep-deprived group (M = 72.3%, SD = 8.1) "
+ "and the control group (M = 89.7%, SD = 5.4), t(46) = 9.12, p < .001. The effect "
+ "size was large (Cohen's d = 2.52), indicating a substantial practical difference.",
"Normal");
// ── Level 2: Discussion ──
AddSampleParagraph(body, "Discussion", "Heading2");
AddSampleParagraph(body,
"The findings of this study are consistent with previous research demonstrating the "
+ "deleterious effects of sleep deprivation on cognitive performance. The magnitude of "
+ "the effect observed here exceeds that reported in meta-analytic reviews, possibly "
+ "due to the use of a more demanding dual n-back paradigm that places greater demands "
+ "on executive control processes.",
"Normal");
// Section properties must be last child of body
body.Append(sectPr);
}
/// <summary>
/// Creates an APA 7 "run-in" heading style (Levels 4 and 5).
/// These headings are indented 0.5in and end with a period;
/// the paragraph text runs in on the same line as the heading.
/// In OpenXML, we create a paragraph style with the appropriate formatting.
/// </summary>
private static Style CreateAPA7RunInHeadingStyle(int level, bool bold, bool italic)
{
var rPr = new StyleRunProperties(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman",
EastAsia = "SimSun",
ComplexScript = "Times New Roman"
},
new FontSize { Val = "24" }, // 12pt — same as body
new FontSizeComplexScript { Val = "24" },
new Color { Val = "000000" }
);
if (bold)
rPr.Append(new Bold());
if (italic)
rPr.Append(new Italic());
var pPr = new StyleParagraphProperties(
new KeepNext(),
new KeepLines(),
new SpacingBetweenLines
{
Before = "480",
After = "0",
Line = "480",
LineRule = LineSpacingRuleValues.Auto
},
// Indented 0.5in = 720 DXA (APA 7 Levels 4-5)
new Indentation { FirstLine = "720" },
new OutlineLevel { Val = level - 1 }
);
return new Style(
new StyleName { Val = $"heading {level}" },
new BasedOn { Val = "Normal" },
new NextParagraphStyle { Val = "Normal" },
new UIPriority { Val = 9 },
new PrimaryStyle(),
pPr,
rPr
)
{
Type = StyleValues.Paragraph,
StyleId = $"Heading{level}",
Default = false
};
}
/// <summary>
/// Creates a centered, optionally bold paragraph style with no first-line indent.
/// Used for APA title page elements and the "Abstract" label.
/// </summary>
private static Style CreateAPA7NoIndentCenteredStyle(string styleId, string styleName, bool bold)
{
var rPr = new StyleRunProperties(
new FontSize { Val = "24" },
new FontSizeComplexScript { Val = "24" }
);
if (bold)
rPr.Append(new Bold());
return new Style(
new StyleName { Val = styleName },
new BasedOn { Val = "Normal" },
new UIPriority { Val = 1 },
new StyleParagraphProperties(
new Justification { Val = JustificationValues.Center },
new Indentation { FirstLine = "0" },
new SpacingBetweenLines
{
Line = "480",
LineRule = LineSpacingRuleValues.Auto,
After = "0"
}
),
rPr
)
{
Type = StyleValues.Paragraph,
StyleId = styleId,
Default = false
};
}
/// <summary>
/// Creates a left-aligned paragraph style with no first-line indent.
/// Used for the abstract body text (APA 7 specifies no indent for abstract).
/// </summary>
private static Style CreateAPA7NoIndentStyle(string styleId, string styleName)
{
return new Style(
new StyleName { Val = styleName },
new BasedOn { Val = "Normal" },
new UIPriority { Val = 1 },
new StyleParagraphProperties(
new Indentation { FirstLine = "0" },
new SpacingBetweenLines
{
Line = "480",
LineRule = LineSpacingRuleValues.Auto,
After = "0"
}
)
)
{
Type = StyleValues.Paragraph,
StyleId = styleId,
Default = false
};
}
/// <summary>
/// Adds the APA 7 professional paper header: running head flush left (ALL CAPS)
/// and page number flush right, both in the same header line.
/// Per APA 7, Section 2.18: the running head appears on every page.
/// </summary>
private static void AddAPA7Header(MainDocumentPart mainPart, SectionProperties sectPr, string runningHeadText)
{
// Use a tab stop at the right margin to position the page number flush right
// Right margin position: page width (12240) - left margin (1440) - right margin (1440) = 9360 DXA
var headerParagraph = new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "Normal" },
new Indentation { FirstLine = "0" },
new SpacingBetweenLines { Line = "240", LineRule = LineSpacingRuleValues.Auto, After = "0" },
new Tabs(
new TabStop
{
Val = TabStopValues.Right,
Position = 9360 // Flush right at the text area edge
}
)
),
// Running head text (flush left, ALL CAPS)
new Run(
new RunProperties(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman"
},
new FontSize { Val = "24" },
new FontSizeComplexScript { Val = "24" }
),
new Text(runningHeadText) { Space = SpaceProcessingModeValues.Preserve }
),
// Tab to move to right-aligned position
new Run(
new RunProperties(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman"
},
new FontSize { Val = "24" },
new FontSizeComplexScript { Val = "24" }
),
new TabChar()
),
// Page number (flush right)
new SimpleField(
new Run(
new RunProperties(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman"
},
new FontSize { Val = "24" },
new FontSizeComplexScript { Val = "24" }
),
new Text("1")
)
)
{ Instruction = " PAGE " }
);
var headerPart = mainPart.AddNewPart<HeaderPart>();
headerPart.Header = new Header(headerParagraph);
headerPart.Header.Save();
string headerPartId = mainPart.GetIdOfPart(headerPart);
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = headerPartId
});
}
/// <summary>
/// Adds the APA 7 title page content: title, author, affiliation,
/// course, instructor, and date — all centered and double-spaced.
/// Per APA 7, Section 2.3: title should be bold, centered, in upper half of page.
/// </summary>
private static void AddAPA7TitlePage(Body body,
string title, string authorName, string affiliation,
string courseLine, string instructorLine, string dateLine)
{
// Add some blank lines to position title in upper half of page
for (int i = 0; i < 3; i++)
{
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "APATitlePageInfo" }
)
));
}
// Title: centered, bold
AddSampleParagraph(body, title, "APATitlePageTitle");
// Author name
AddSampleParagraph(body, authorName, "APATitlePageInfo");
// Affiliation
AddSampleParagraph(body, affiliation, "APATitlePageInfo");
// Course
AddSampleParagraph(body, courseLine, "APATitlePageInfo");
// Instructor
AddSampleParagraph(body, instructorLine, "APATitlePageInfo");
// Date
AddSampleParagraph(body, dateLine, "APATitlePageInfo");
// Page break after title page
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "APATitlePageInfo" }
),
new Run(new Break { Type = BreakValues.Page })
));
}
/// <summary>
/// Creates an APA Level 4 or 5 "run-in" paragraph where the heading text
/// (bold or bold italic) is followed by the body text on the same line.
/// The heading ends with a period per APA 7 convention.
/// </summary>
private static Paragraph CreateAPA7RunInParagraph(
string headingText, string bodyText, bool bold, bool italic)
{
var headingRunProps = new RunProperties(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman",
ComplexScript = "Times New Roman"
},
new FontSize { Val = "24" },
new FontSizeComplexScript { Val = "24" }
);
if (bold)
headingRunProps.Append(new Bold());
if (italic)
headingRunProps.Append(new Italic());
return new Paragraph(
new ParagraphProperties(
new Indentation { FirstLine = "720" }, // 0.5in indent
new SpacingBetweenLines
{
Line = "480",
LineRule = LineSpacingRuleValues.Auto,
After = "0"
}
),
// Heading run (bold / bold italic)
new Run(
headingRunProps,
new Text(headingText) { Space = SpaceProcessingModeValues.Preserve }
),
// Body text run (regular)
new Run(
new RunProperties(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman",
ComplexScript = "Times New Roman"
},
new FontSize { Val = "24" },
new FontSizeComplexScript { Val = "24" }
),
new Text(bodyText) { Space = SpaceProcessingModeValues.Preserve }
)
);
}
// ════════════════════════════════════════════════════════════════════════
// RECIPE 9: MLA 9TH EDITION
// ════════════════════════════════════════════════════════════════════════
/// <summary>
/// Recipe: MLA 9th Edition
/// Source: MLA Handbook, 9th edition (2021), Part 1 (Principles of Scholarship)
/// and Part 2 (Details of MLA Style).
///
/// Key MLA 9 specifications:
/// - Font: 12pt Times New Roman (or other readable font; Times New Roman is standard).
/// - Margins: 1 inch on all sides.
/// - Line spacing: Double-spaced throughout, including block quotes and Works Cited.
/// - Paragraph indent: 0.5 inch first-line indent for body paragraphs.
/// - Title: Centered, same size as body text (12pt), NOT bold, italic, or underlined.
/// MLA eschews visual hierarchy — the title is distinguished only by centering.
/// - No mandatory heading system. If headings are used, they should be simple and
/// consistent. MLA does not prescribe heading levels like APA does.
/// - Running header: Author's last name and page number, flush right, 0.5 inch from top.
/// - First-page header block: Student's name, instructor's name, course title, and
/// date — upper left, double-spaced, NO extra spacing.
/// - Works Cited: title "Works Cited" centered (not bold), entries have hanging indent
/// of 0.5 inch (first line flush left, subsequent lines indented).
/// - No title page required (unless specifically requested by instructor).
///
/// Design rationale:
/// - MLA's aesthetic is deliberately plain — the writing is the content.
/// - No bold headings, no size variation, no decorative elements.
/// - The only structural markers are centering (title, Works Cited label)
/// and indentation (paragraphs, hanging indent for citations).
/// - This uniformity reflects MLA's roots in literary studies, where the
/// text itself is paramount and formatting should be invisible.
/// </summary>
public static void CreateMLA9Document(string outputPath)
{
using var doc = WordprocessingDocument.Create(outputPath, WordprocessingDocumentType.Document);
var mainPart = doc.AddMainDocumentPart();
mainPart.Document = new Document(new Body());
var body = mainPart.Document.Body!;
// ── Styles ──
var stylesPart = mainPart.AddNewPart<StyleDefinitionsPart>();
stylesPart.Styles = new Styles();
var styles = stylesPart.Styles;
// DocDefaults: 12pt Times New Roman, double spacing, 0.5in first-line indent
styles.Append(new DocDefaults(
new RunPropertiesDefault(
new RunPropertiesBaseStyle(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman",
EastAsia = "SimSun",
ComplexScript = "Times New Roman"
},
new FontSize { Val = "24" }, // 12pt
new FontSizeComplexScript { Val = "24" },
new Color { Val = "000000" },
new Languages { Val = "en-US", EastAsia = "zh-CN" }
)
),
new ParagraphPropertiesDefault(
new ParagraphPropertiesBaseStyle(
new SpacingBetweenLines
{
Line = "480", // Double spacing throughout
LineRule = LineSpacingRuleValues.Auto,
After = "0"
},
new Indentation { FirstLine = "720" } // 0.5in first-line indent
)
)
));
// ── Normal style ──
styles.Append(CreateParagraphStyle(
styleId: "Normal",
styleName: "Normal",
isDefault: true,
uiPriority: 0
));
// ── MLA Title style: centered, NOT bold/italic/underlined ──
// MLA is distinctive: the title has NO special formatting beyond centering.
styles.Append(CreateMLA9TitleStyle());
// ── MLA Header Block style: flush left, no indent ──
styles.Append(CreateMLA9HeaderBlockStyle());
// ── MLA Works Cited label style: centered, not bold ──
styles.Append(CreateMLA9WorksCitedLabelStyle());
// ── MLA Works Cited entry style: hanging indent 0.5in ──
styles.Append(CreateMLA9WorksCitedEntryStyle());
// ── Page setup: US Letter, 1in all sides ──
var sectPr = new SectionProperties(
new WpPageSize { Width = 12240U, Height = 15840U },
new PageMargin
{
Top = 1440, Bottom = 1440,
Left = 1440U, Right = 1440U,
Header = 720U, Footer = 720U, Gutter = 0U
}
);
// ── Running header: "LastName PageNumber" flush right ──
AddMLA9Header(mainPart, sectPr, "Mitchell");
// ══════════════════════════════════════════════════════════════════
// SAMPLE CONTENT: MLA header block, title, body, Works Cited
// ══════════════════════════════════════════════════════════════════
// ── First-page header block (upper left, double-spaced) ──
AddSampleParagraph(body, "Sarah Mitchell", "MLAHeaderBlock");
AddSampleParagraph(body, "Professor Johnson", "MLAHeaderBlock");
AddSampleParagraph(body, "English 201: American Literature", "MLAHeaderBlock");
AddSampleParagraph(body, "15 October 2024", "MLAHeaderBlock");
// ── Title: centered, 12pt, plain (not bold) ──
AddSampleParagraph(body, "The Function of the Unreliable Narrator in Nabokov's Lolita", "MLATitle");
// ── Body paragraphs ──
AddSampleParagraph(body,
"Vladimir Nabokov's Lolita (1955) remains one of the most studied examples of "
+ "unreliable narration in twentieth-century fiction. Humbert Humbert's elaborate, "
+ "self-justifying prose has been analyzed through numerous critical lenses, yet the "
+ "question of how the novel's narrative structure shapes reader complicity continues "
+ "to generate scholarly debate.",
"Normal");
AddSampleParagraph(body,
"The concept of the unreliable narrator, first articulated by Wayne C. Booth in "
+ "The Rhetoric of Fiction (1961), provides a foundational framework for understanding "
+ "Humbert's discourse. Booth argues that unreliable narrators are those whose values "
+ "diverge from those of the implied author (158-59). In Lolita, this divergence is "
+ "particularly complex because Nabokov layers multiple forms of unreliability: "
+ "factual, evaluative, and interpretive.",
"Normal");
AddSampleParagraph(body,
"Michael Wood has observed that \"Nabokov's genius lies in making us forget, "
+ "momentarily, that Humbert is a monster\" (127). This temporary forgetting is not "
+ "a failure of reading but a designed effect of the narrative voice. The luxurious "
+ "prose, the literary allusions, the self-deprecating wit \u2014 all serve to create what "
+ "Nomi Tamir-Ghez calls \"rhetorical seduction\" (42), in which readers find "
+ "themselves sympathizing with a narrator whose actions they would condemn.",
"Normal");
AddSampleParagraph(body,
"The structural implications of Humbert's unreliability extend beyond mere "
+ "factual distortion. As Eric Naiman demonstrates, the novel's famous opening "
+ "paragraph \u2014 with its incantatory repetition of \"Lolita\" \u2014 establishes a "
+ "pattern of linguistic possession that mirrors Humbert's physical possession of "
+ "Dolores Haze (85). The language itself becomes an instrument of control, one "
+ "that operates on the reader as well as on the characters within the narrative.",
"Normal");
// ── Works Cited ──
// Page break before Works Cited
body.Append(new Paragraph(
new ParagraphProperties(
new ParagraphStyleId { Val = "MLAHeaderBlock" }
),
new Run(new Break { Type = BreakValues.Page })
));
AddSampleParagraph(body, "Works Cited", "MLAWorksCitedLabel");
// Works Cited entries with hanging indent
AddSampleParagraph(body,
"Booth, Wayne C. The Rhetoric of Fiction. 2nd ed., U of Chicago P, 1983.",
"MLAWorksCitedEntry");
AddSampleParagraph(body,
"Nabokov, Vladimir. Lolita. 1955. Vintage International, 1989.",
"MLAWorksCitedEntry");
AddSampleParagraph(body,
"Naiman, Eric. Nabokov, Perversely. Cornell UP, 2010.",
"MLAWorksCitedEntry");
AddSampleParagraph(body,
"Tamir-Ghez, Nomi. \"The Art of Persuasion in Nabokov's Lolita.\" Poetics Today, "
+ "vol. 1, no. 1-2, 1979, pp. 65-83.",
"MLAWorksCitedEntry");
AddSampleParagraph(body,
"Wood, Michael. The Magician's Doubts: Nabokov and the Risks of Fiction. "
+ "Princeton UP, 1995.",
"MLAWorksCitedEntry");
// Section properties must be last child of body
body.Append(sectPr);
}
/// <summary>
/// MLA title style: centered, 12pt, NO bold/italic/underline.
/// MLA's radical plainness — the title is distinguished only by position.
/// </summary>
private static Style CreateMLA9TitleStyle()
{
return new Style(
new StyleName { Val = "MLA Title" },
new BasedOn { Val = "Normal" },
new UIPriority { Val = 1 },
new StyleParagraphProperties(
new Justification { Val = JustificationValues.Center },
new Indentation { FirstLine = "0" },
new SpacingBetweenLines
{
Line = "480",
LineRule = LineSpacingRuleValues.Auto,
After = "0"
}
)
)
{
Type = StyleValues.Paragraph,
StyleId = "MLATitle",
Default = false
};
}
/// <summary>
/// MLA first-page header block style: flush left, no first-line indent, double-spaced.
/// Used for the student name, instructor, course, and date lines.
/// </summary>
private static Style CreateMLA9HeaderBlockStyle()
{
return new Style(
new StyleName { Val = "MLA Header Block" },
new BasedOn { Val = "Normal" },
new UIPriority { Val = 1 },
new StyleParagraphProperties(
new Justification { Val = JustificationValues.Left },
new Indentation { FirstLine = "0" },
new SpacingBetweenLines
{
Line = "480",
LineRule = LineSpacingRuleValues.Auto,
After = "0"
}
)
)
{
Type = StyleValues.Paragraph,
StyleId = "MLAHeaderBlock",
Default = false
};
}
/// <summary>
/// MLA Works Cited label style: centered, 12pt, NOT bold.
/// Like the title, the label is plain — only centering distinguishes it.
/// </summary>
private static Style CreateMLA9WorksCitedLabelStyle()
{
return new Style(
new StyleName { Val = "MLA Works Cited Label" },
new BasedOn { Val = "Normal" },
new UIPriority { Val = 1 },
new StyleParagraphProperties(
new Justification { Val = JustificationValues.Center },
new Indentation { FirstLine = "0" },
new SpacingBetweenLines
{
Line = "480",
LineRule = LineSpacingRuleValues.Auto,
After = "0"
}
)
)
{
Type = StyleValues.Paragraph,
StyleId = "MLAWorksCitedLabel",
Default = false
};
}
/// <summary>
/// MLA Works Cited entry style: hanging indent of 0.5 inch (720 DXA).
/// First line is flush left; subsequent lines indent 0.5 inch.
/// This is the standard format for bibliography entries in MLA style.
/// </summary>
private static Style CreateMLA9WorksCitedEntryStyle()
{
return new Style(
new StyleName { Val = "MLA Works Cited Entry" },
new BasedOn { Val = "Normal" },
new UIPriority { Val = 1 },
new StyleParagraphProperties(
new Justification { Val = JustificationValues.Left },
// Hanging indent: Left = 720, FirstLine is negative (Hanging = 720)
new Indentation { Left = "720", Hanging = "720" },
new SpacingBetweenLines
{
Line = "480",
LineRule = LineSpacingRuleValues.Auto,
After = "0"
}
)
)
{
Type = StyleValues.Paragraph,
StyleId = "MLAWorksCitedEntry",
Default = false
};
}
/// <summary>
/// Adds the MLA 9 running header: author last name and page number, flush right,
/// 0.5 inch from top of page. Per MLA convention, this appears on every page.
/// </summary>
private static void AddMLA9Header(MainDocumentPart mainPart, SectionProperties sectPr, string authorLastName)
{
var headerParagraph = new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Right },
new Indentation { FirstLine = "0" },
new SpacingBetweenLines { Line = "240", LineRule = LineSpacingRuleValues.Auto, After = "0" }
),
// Author last name
new Run(
new RunProperties(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman"
},
new FontSize { Val = "24" },
new FontSizeComplexScript { Val = "24" }
),
new Text(authorLastName + " ") { Space = SpaceProcessingModeValues.Preserve }
),
// Page number
new SimpleField(
new Run(
new RunProperties(
new RunFonts
{
Ascii = "Times New Roman",
HighAnsi = "Times New Roman"
},
new FontSize { Val = "24" },
new FontSizeComplexScript { Val = "24" }
),
new Text("1")
)
)
{ Instruction = " PAGE " }
);
var headerPart = mainPart.AddNewPart<HeaderPart>();
headerPart.Header = new Header(headerParagraph);
headerPart.Header.Save();
string headerPartId = mainPart.GetIdOfPart(headerPart);
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = headerPartId
});
}
}
@@ -0,0 +1,624 @@
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
namespace MiniMaxAIDocx.Core.Samples;
/// <summary>
/// Reference implementations for field codes and Table of Contents (TOC).
///
/// KEY CONCEPTS:
/// - SimpleField: single-element shorthand, e.g. &lt;w:fldSimple w:instr="PAGE"/&gt;
/// - Complex field: three FieldChar elements (Begin / Separate / End) with FieldCode between them.
/// Word always writes complex fields; SimpleField is only used for trivial cases.
/// - TOC is a structured document tag (SdtBlock) wrapping a complex field.
/// - UpdateFieldsOnOpen tells Word to recalculate all fields when opening.
/// </summary>
public static class FieldAndTocSamples
{
// ──────────────────────────────────────────────
// 1. InsertToc — TOC levels 1-3 inside SdtBlock
// ──────────────────────────────────────────────
/// <summary>
/// Inserts a Table of Contents covering heading levels 1-3.
/// Uses an SdtBlock wrapper with a complex field code:
/// TOC \o "1-3" \h \z \u
///
/// Switches:
/// \o "1-3" — outline levels 1-3
/// \h — hyperlinks
/// \z — hide tab leaders / page numbers in Web Layout
/// \u — use applied paragraph outline level
/// </summary>
public static SdtBlock InsertToc(Body body)
{
var sdtBlock = new SdtBlock();
// SdtProperties — mark as TOC
var sdtPr = new SdtProperties();
sdtPr.Append(new SdtContentDocPartObject(
new DocPartGallery { Val = "Table of Contents" },
new DocPartUnique()));
sdtBlock.Append(sdtPr);
// SdtContent — contains the field code paragraph(s)
var sdtContent = new SdtContentBlock();
// TOC title paragraph
var titlePara = new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "TOCHeading" }),
new Run(new Text("Table of Contents")));
sdtContent.Append(titlePara);
// Complex field paragraph for TOC
var fieldPara = new Paragraph();
InsertComplexFieldInline(fieldPara, " TOC \\o \"1-3\" \\h \\z \\u ");
sdtContent.Append(fieldPara);
sdtBlock.Append(sdtContent);
body.Append(sdtBlock);
return sdtBlock;
}
// ──────────────────────────────────────────────
// 2. InsertTocWithCustomLevels — TOC 1-4 levels
// ──────────────────────────────────────────────
/// <summary>
/// Inserts a TOC covering heading levels 1-4.
/// Identical structure to <see cref="InsertToc"/> but with "\o 1-4".
/// </summary>
public static SdtBlock InsertTocWithCustomLevels(Body body)
{
var sdtBlock = new SdtBlock();
var sdtPr = new SdtProperties();
sdtPr.Append(new SdtContentDocPartObject(
new DocPartGallery { Val = "Table of Contents" },
new DocPartUnique()));
sdtBlock.Append(sdtPr);
var sdtContent = new SdtContentBlock();
var titlePara = new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "TOCHeading" }),
new Run(new Text("Table of Contents")));
sdtContent.Append(titlePara);
// 1-4 levels instead of 1-3
var fieldPara = new Paragraph();
InsertComplexFieldInline(fieldPara, " TOC \\o \"1-4\" \\h \\z \\u ");
sdtContent.Append(fieldPara);
sdtBlock.Append(sdtContent);
body.Append(sdtBlock);
return sdtBlock;
}
// ──────────────────────────────────────────────
// 3. InsertSimpleField — PAGE, NUMPAGES, DATE, etc.
// ──────────────────────────────────────────────
/// <summary>
/// Inserts a SimpleField element into a paragraph.
///
/// SimpleField is the compact form: &lt;w:fldSimple w:instr=" PAGE "&gt;&lt;w:r&gt;...&lt;/w:r&gt;&lt;/w:fldSimple&gt;
///
/// Common instructions: "PAGE", "NUMPAGES", "DATE", "TIME", "FILENAME".
/// The run inside is the cached display value; Word recalculates on open.
/// </summary>
public static SimpleField InsertSimpleField(Paragraph para, string instruction)
{
var simpleField = new SimpleField { Instruction = $" {instruction} " };
// Cached display value — Word replaces this on recalculation
simpleField.Append(new Run(
new RunProperties(new NoProof()),
new Text("«" + instruction + "»")));
para.Append(simpleField);
return simpleField;
}
// ──────────────────────────────────────────────
// 4. InsertComplexField — Begin/Separate/End
// ──────────────────────────────────────────────
/// <summary>
/// Inserts a complex field into a paragraph using the FieldChar Begin/Separate/End pattern.
///
/// Structure:
/// Run1: FieldChar(Begin) + FieldCode(" PAGE ")
/// Run2: FieldChar(Separate)
/// Run3: Text("1") ← cached display value
/// Run4: FieldChar(End)
///
/// Use complex fields when you need dirty flags, lock, or nested fields.
/// </summary>
public static void InsertComplexField(Paragraph para, string instruction)
{
InsertComplexFieldInline(para, $" {instruction} ");
}
// ──────────────────────────────────────────────
// 5. InsertDateField — DATE with format switch
// ──────────────────────────────────────────────
/// <summary>
/// Inserts a DATE field with a format switch: DATE \@ "yyyy-MM-dd"
///
/// The \@ switch specifies the date/time picture.
/// Common formats:
/// \@ "yyyy-MM-dd" → 2026-03-22
/// \@ "MMMM d, yyyy" → March 22, 2026
/// \@ "M/d/yyyy h:mm am/pm" → 3/22/2026 2:30 PM
/// </summary>
public static void InsertDateField(Paragraph para, string format)
{
// Field instruction with date-time picture switch
string instruction = $" DATE \\@ \"{format}\" ";
InsertComplexFieldInline(para, instruction);
}
// ──────────────────────────────────────────────
// 6. InsertCrossReference — REF field
// ──────────────────────────────────────────────
/// <summary>
/// Inserts a REF cross-reference field that refers to a bookmark.
///
/// Instruction: REF bookmarkName \h
/// \h — creates a hyperlink to the bookmark
/// \p — inserts "above" or "below" relative position
/// \n — inserts paragraph number of the bookmark
/// </summary>
public static void InsertCrossReference(Paragraph para, string bookmarkName)
{
string instruction = $" REF {bookmarkName} \\h ";
InsertComplexFieldInline(para, instruction);
}
// ──────────────────────────────────────────────
// 7. InsertSequenceField — SEQ for numbering
// ──────────────────────────────────────────────
/// <summary>
/// Inserts a SEQ (sequence) field for auto-numbering figures, tables, etc.
///
/// Usage pattern for "Figure 1":
/// 1. Append a run with text "Figure " to the paragraph
/// 2. Call InsertSequenceField(para, "Figure")
///
/// Usage pattern for "Table 1":
/// 1. Append a run with text "Table " to the paragraph
/// 2. Call InsertSequenceField(para, "Table")
///
/// Each unique seqName maintains its own counter across the document.
/// </summary>
public static void InsertSequenceField(Paragraph para, string seqName)
{
string instruction = $" SEQ {seqName} \\* ARABIC ";
InsertComplexFieldInline(para, instruction);
}
// ──────────────────────────────────────────────
// 8. InsertMergeField — MERGEFIELD for mail merge
// ──────────────────────────────────────────────
/// <summary>
/// Inserts a MERGEFIELD for mail merge scenarios.
///
/// Instruction: MERGEFIELD fieldName \* MERGEFORMAT
/// \* MERGEFORMAT — preserves formatting applied to the field result
/// \b "text" — text before if field is non-empty
/// \f "text" — text after if field is non-empty
///
/// The cached display shows «fieldName» as a placeholder.
/// </summary>
public static void InsertMergeField(Paragraph para, string fieldName)
{
string instruction = $" MERGEFIELD {fieldName} \\* MERGEFORMAT ";
// Begin
para.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.Begin }));
// Field code
para.Append(new Run(
new FieldCode(instruction) { Space = SpaceProcessingModeValues.Preserve }));
// Separate
para.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.Separate }));
// Cached value — shows merge field placeholder
para.Append(new Run(
new RunProperties(new NoProof()),
new Text($"\u00AB{fieldName}\u00BB") { Space = SpaceProcessingModeValues.Preserve }));
// End
para.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.End }));
}
// ──────────────────────────────────────────────
// 9. InsertConditionalField — IF field
// ──────────────────────────────────────────────
/// <summary>
/// Inserts an IF conditional field.
///
/// Syntax: IF expression1 operator expression2 "true-text" "false-text"
/// Example: IF { MERGEFIELD Gender } = "Male" "Mr." "Ms."
///
/// This example checks if MERGEFIELD Amount > 1000 and displays different text.
/// Nested fields (MERGEFIELD inside IF) require nested Begin/End pairs.
/// </summary>
public static void InsertConditionalField(Paragraph para)
{
// Outer IF field Begin
para.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.Begin }));
para.Append(new Run(
new FieldCode(" IF ") { Space = SpaceProcessingModeValues.Preserve }));
// Nested MERGEFIELD inside the IF condition
para.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.Begin }));
para.Append(new Run(
new FieldCode(" MERGEFIELD Amount ") { Space = SpaceProcessingModeValues.Preserve }));
para.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.Separate }));
para.Append(new Run(
new Text("0") { Space = SpaceProcessingModeValues.Preserve }));
para.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.End }));
// Continuation of IF instruction
para.Append(new Run(
new FieldCode(" > \"1000\" \"High Value\" \"Standard\" ") { Space = SpaceProcessingModeValues.Preserve }));
// Separate — cached result
para.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.Separate }));
para.Append(new Run(
new RunProperties(new NoProof()),
new Text("Standard") { Space = SpaceProcessingModeValues.Preserve }));
// End
para.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.End }));
}
// ──────────────────────────────────────────────
// 10. InsertStyleRef — STYLEREF for running headers
// ──────────────────────────────────────────────
/// <summary>
/// Inserts a STYLEREF field, commonly used in headers/footers
/// to display the current chapter or section title.
///
/// Instruction: STYLEREF "Heading 1"
/// Displays the text of the nearest paragraph with style "Heading 1".
/// \l — search from bottom of page up (for last instance on page)
/// \n — insert the paragraph number, not text
/// </summary>
public static void InsertStyleRef(Paragraph para)
{
string instruction = " STYLEREF \"Heading 1\" ";
InsertComplexFieldInline(para, instruction);
}
// ──────────────────────────────────────────────
// 11. EnableUpdateFieldsOnOpen
// ──────────────────────────────────────────────
/// <summary>
/// Sets the UpdateFieldsOnOpen property so Word recalculates
/// all fields (PAGE, TOC, SEQ, etc.) when the document is opened.
///
/// Without this, TOC and cross-references show stale cached values
/// until the user manually presses Ctrl+A, F9 to update.
/// </summary>
public static void EnableUpdateFieldsOnOpen(DocumentSettingsPart settingsPart)
{
settingsPart.Settings ??= new Settings();
var existing = settingsPart.Settings.GetFirstChild<UpdateFieldsOnOpen>();
if (existing != null)
{
existing.Val = true;
}
else
{
settingsPart.Settings.Append(new UpdateFieldsOnOpen { Val = true });
}
settingsPart.Settings.Save();
}
// ──────────────────────────────────────────────
// 12. CreateTocStyles — TOC1/2/3 with tab leaders
// ──────────────────────────────────────────────
/// <summary>
/// Creates TOC1, TOC2, TOC3 paragraph styles with right-aligned tab stops
/// and dot leaders (the "....." between entry text and page number).
///
/// Each TOC level is indented further:
/// TOC1 — 0 indent
/// TOC2 — 240 twips (1/6 inch)
/// TOC3 — 480 twips (1/3 inch)
///
/// Tab leader: dot-filled right tab at 9360 twips (6.5 inches for letter paper).
/// </summary>
public static void CreateTocStyles(StyleDefinitionsPart stylesPart)
{
stylesPart.Styles ??= new Styles();
string[] tocStyleIds = ["TOC1", "TOC2", "TOC3"];
string[] tocStyleNames = ["toc 1", "toc 2", "toc 3"];
int[] indents = [0, 240, 480]; // twips
// Right tab position: 6.5 inches = 9360 twips (standard for US Letter)
const int tabPosition = 9360;
for (int i = 0; i < tocStyleIds.Length; i++)
{
var style = new Style
{
Type = StyleValues.Paragraph,
StyleId = tocStyleIds[i],
CustomStyle = false
};
style.Append(new StyleName { Val = tocStyleNames[i] });
style.Append(new BasedOn { Val = "Normal" });
style.Append(new NextParagraphStyle { Val = "Normal" });
style.Append(new UIPriority { Val = 39 });
var pPr = new StyleParagraphProperties();
// Indentation for nested levels
if (indents[i] > 0)
{
pPr.Append(new Indentation { Left = indents[i].ToString() });
}
// Spacing: no space after for compact TOC
pPr.Append(new SpacingBetweenLines { After = "0", Line = "276", LineRule = LineSpacingRuleValues.Auto });
// Right-aligned tab with dot leader
var tabs = new Tabs();
tabs.Append(new TabStop
{
Val = TabStopValues.Right,
Leader = TabStopLeaderCharValues.Dot,
Position = tabPosition
});
pPr.Append(tabs);
style.Append(pPr);
stylesPart.Styles.Append(style);
}
stylesPart.Styles.Save();
}
// ──────────────────────────────────────────────
// 13. CreateMixedTocStructure — Real-world TOC
// ──────────────────────────────────────────────
/// <summary>
/// Real-world TOC structure: Mixed SDT block + static entries + field code.
///
/// IMPORTANT: Most templates do NOT have a clean TOC field code alone.
/// Instead, they contain:
/// 1. An SDT (Structured Document Tag) wrapper with alias "TOC"
/// 2. Inside the SDT: a field code BEGIN + SEPARATE + static example entries + END
/// 3. The static entries are placeholder text (e.g., "第1章 绪论...........1")
/// that Word replaces when user presses "Update Fields"
///
/// When applying a template (Scenario C), you should:
/// - KEEP the entire SDT block from the template (don't rebuild it)
/// - DO NOT replace static entries with programmatic content
/// - The entries will auto-update when the user opens in Word and updates fields
/// - If you must update entries programmatically, replace the content INSIDE
/// the SDT between fldChar separate and fldChar end
///
/// Common mistake: Treating TOC as pure field code and rebuilding it from scratch,
/// which destroys the SDT wrapper and breaks Word's "Update Table" functionality.
/// </summary>
public static void CreateMixedTocStructure(string outputPath)
{
using var doc = WordprocessingDocument.Create(outputPath, WordprocessingDocumentType.Document);
var mainPart = doc.AddMainDocumentPart();
mainPart.Document = new Document();
var body = new Body();
mainPart.Document.Append(body);
// Add styles part with TOC styles
var stylesPart = mainPart.AddNewPart<StyleDefinitionsPart>();
CreateTocStyles(stylesPart);
// ─── SDT Block wrapping the entire TOC ───
var sdtBlock = new SdtBlock();
// SDT Properties: alias "TOC", tag, and DocPartGallery
var sdtPr = new SdtProperties();
sdtPr.Append(new SdtAlias { Val = "TOC" });
sdtPr.Append(new Tag { Val = "TOC" });
sdtPr.Append(new SdtContentDocPartObject(
new DocPartGallery { Val = "Table of Contents" },
new DocPartUnique()));
sdtBlock.Append(sdtPr);
// SDT Content: field code + static entries
var sdtContent = new SdtContentBlock();
// ─── TOC title paragraph ───
var titlePara = new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "TOCHeading" }),
new Run(new Text("目 录")));
sdtContent.Append(titlePara);
// ─── Field code BEGIN paragraph ───
var fieldBeginPara = new Paragraph();
// fldChar Begin
fieldBeginPara.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.Begin }));
// instrText: TOC \o "1-3" \h \z \u
fieldBeginPara.Append(new Run(
new FieldCode(" TOC \\o \"1-3\" \\h \\z \\u ") { Space = SpaceProcessingModeValues.Preserve }));
// fldChar Separate
fieldBeginPara.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.Separate }));
sdtContent.Append(fieldBeginPara);
// ─── Static placeholder entries (TOC1/TOC2/TOC3) ───
// These are the example entries that Word will replace when user clicks "Update Table".
// In real templates, these show example chapter titles with dot leaders and page numbers.
// TOC level 1 entry: "第1章 绪论...........1"
sdtContent.Append(CreateStaticTocEntry("TOC1", "第1章 绪论", "1"));
// TOC level 2 entry: "1.1 研究背景...........1"
sdtContent.Append(CreateStaticTocEntry("TOC2", "1.1 研究背景", "1"));
// TOC level 2 entry: "1.2 研究目的...........2"
sdtContent.Append(CreateStaticTocEntry("TOC2", "1.2 研究目的", "2"));
// TOC level 1 entry: "第2章 文献综述...........3"
sdtContent.Append(CreateStaticTocEntry("TOC1", "第2章 文献综述", "3"));
// TOC level 2 entry: "2.1 国内研究现状...........3"
sdtContent.Append(CreateStaticTocEntry("TOC2", "2.1 国内研究现状", "3"));
// TOC level 3 entry: "2.1.1 早期研究...........4"
sdtContent.Append(CreateStaticTocEntry("TOC3", "2.1.1 早期研究", "4"));
// TOC level 1 entry: "第3章 研究方法...........5"
sdtContent.Append(CreateStaticTocEntry("TOC1", "第3章 研究方法", "5"));
// ─── Field code END paragraph ───
var fieldEndPara = new Paragraph();
fieldEndPara.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.End }));
sdtContent.Append(fieldEndPara);
sdtBlock.Append(sdtContent);
body.Append(sdtBlock);
// ─── Actual heading paragraphs (what the TOC references) ───
body.Append(new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Heading1" }),
new Run(new Text("第1章 绪论"))));
body.Append(new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Heading2" }),
new Run(new Text("1.1 研究背景"))));
body.Append(new Paragraph(
new Run(new Text("本研究旨在探讨……"))));
body.Append(new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Heading2" }),
new Run(new Text("1.2 研究目的"))));
body.Append(new Paragraph(
new Run(new Text("研究目的包括……"))));
body.Append(new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Heading1" }),
new Run(new Text("第2章 文献综述"))));
body.Append(new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Heading2" }),
new Run(new Text("2.1 国内研究现状"))));
body.Append(new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Heading3" }),
new Run(new Text("2.1.1 早期研究"))));
body.Append(new Paragraph(
new Run(new Text("早期研究表明……"))));
body.Append(new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Heading1" }),
new Run(new Text("第3章 研究方法"))));
body.Append(new Paragraph(
new Run(new Text("本章介绍研究方法……"))));
// ─── Enable UpdateFieldsOnOpen so TOC auto-refreshes ───
var settingsPart = mainPart.AddNewPart<DocumentSettingsPart>();
EnableUpdateFieldsOnOpen(settingsPart);
mainPart.Document.Save();
}
/// <summary>
/// Helper: creates a single static TOC entry paragraph with style, text, tab leader, and page number.
/// This mirrors what Word generates inside a TOC SDT block.
/// </summary>
private static Paragraph CreateStaticTocEntry(string tocStyleId, string entryText, string pageNumber)
{
var para = new Paragraph();
// Paragraph properties: TOC style + right-aligned tab with dot leader
var pPr = new ParagraphProperties();
pPr.Append(new ParagraphStyleId { Val = tocStyleId });
para.Append(pPr);
// Run with entry text
para.Append(new Run(
new RunProperties(new NoProof()),
new Text(entryText) { Space = SpaceProcessingModeValues.Preserve }));
// Tab character (creates the dot leader between text and page number)
para.Append(new Run(new TabChar()));
// Page number
para.Append(new Run(
new RunProperties(new NoProof()),
new Text(pageNumber)));
return para;
}
// ──────────────────────────────────────────────
// Private helper: insert complex field inline
// ──────────────────────────────────────────────
/// <summary>
/// Shared helper that appends Begin / FieldCode / Separate / CachedValue / End
/// runs to a paragraph.
/// </summary>
private static void InsertComplexFieldInline(Paragraph para, string instruction)
{
// Run 1: FieldChar Begin
para.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.Begin }));
// Run 2: FieldCode (the instruction text)
para.Append(new Run(
new FieldCode(instruction) { Space = SpaceProcessingModeValues.Preserve }));
// Run 3: FieldChar Separate
para.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.Separate }));
// Run 4: Cached display value (placeholder until Word recalculates)
para.Append(new Run(
new RunProperties(new NoProof()),
new Text("1") { Space = SpaceProcessingModeValues.Preserve }));
// Run 5: FieldChar End
para.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.End }));
}
}
@@ -0,0 +1,675 @@
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
// W15 types for people.xml (Office 2013+ comment author tracking)
using W15Person = DocumentFormat.OpenXml.Office2013.Word.Person;
using W15People = DocumentFormat.OpenXml.Office2013.Word.People;
using W15PresenceInfo = DocumentFormat.OpenXml.Office2013.Word.PresenceInfo;
namespace MiniMaxAIDocx.Core.Samples;
/// <summary>
/// Reference implementations for footnotes, endnotes, comments, bookmarks, and hyperlinks.
///
/// KEY CONCEPTS:
/// - FootnotesPart must contain separator (id=-1) and continuationSeparator (id=0) footnotes.
/// - Comments require up to 4 parts: comments.xml, commentsExtended.xml, commentsIds.xml, people.xml.
/// - CommentRangeStart/CommentRangeEnd wrap the commented text; CommentReference goes in a run after CommentRangeEnd.
/// - Bookmarks use BookmarkStart/BookmarkEnd pairs with matching Id attributes.
/// - External hyperlinks require a HyperlinkRelationship in the part's relationships.
/// </summary>
public static class FootnoteAndCommentSamples
{
// ──────────────────────────────────────────────
// 1. SetupFootnotesPart — required separator footnotes
// ──────────────────────────────────────────────
/// <summary>
/// Initializes the FootnotesPart with the two REQUIRED special footnotes:
/// - id=-1: separator (the short horizontal line between body text and footnotes)
/// - id=0: continuationSeparator (line shown when a footnote spans pages)
///
/// Word will refuse to render footnotes correctly without these.
/// Call this once before adding any footnotes.
/// </summary>
public static FootnotesPart SetupFootnotesPart(MainDocumentPart mainPart)
{
var footnotesPart = mainPart.FootnotesPart
?? mainPart.AddNewPart<FootnotesPart>();
footnotesPart.Footnotes = new Footnotes();
// Separator footnote (id = -1): renders as a short horizontal rule
var separator = new Footnote { Type = FootnoteEndnoteValues.Separator, Id = -1 };
separator.Append(new Paragraph(
new ParagraphProperties(new SpacingBetweenLines { After = "0", Line = "240", LineRule = LineSpacingRuleValues.Auto }),
new Run(new SeparatorMark())));
footnotesPart.Footnotes.Append(separator);
// Continuation separator footnote (id = 0): renders as a full-width rule
var contSeparator = new Footnote { Type = FootnoteEndnoteValues.ContinuationSeparator, Id = 0 };
contSeparator.Append(new Paragraph(
new ParagraphProperties(new SpacingBetweenLines { After = "0", Line = "240", LineRule = LineSpacingRuleValues.Auto }),
new Run(new ContinuationSeparatorMark())));
footnotesPart.Footnotes.Append(contSeparator);
footnotesPart.Footnotes.Save();
return footnotesPart;
}
// ──────────────────────────────────────────────
// 2. AddFootnote — reference in body + content in part
// ──────────────────────────────────────────────
/// <summary>
/// Adds a footnote with two coordinated pieces:
/// 1. A FootnoteReference in the body paragraph (superscript number in the text)
/// 2. A Footnote element in the FootnotesPart (the actual footnote content)
///
/// The footnote id links the two together. IDs must be unique and > 0
/// (ids -1 and 0 are reserved for separator and continuationSeparator).
/// </summary>
public static int AddFootnote(MainDocumentPart mainPart, Paragraph para, string footnoteText)
{
// Ensure footnotes part exists with separators
if (mainPart.FootnotesPart == null)
{
SetupFootnotesPart(mainPart);
}
int footnoteId = GetNextFootnoteId(mainPart.FootnotesPart!);
// 1. Add the footnote reference in the body paragraph
// This renders the superscript number (e.g., "1") in the text
var refRun = new Run(
new RunProperties(new VerticalTextAlignment { Val = VerticalPositionValues.Superscript }),
new FootnoteReference { Id = footnoteId });
para.Append(refRun);
// 2. Add the footnote content in the FootnotesPart
var footnote = new Footnote { Id = footnoteId };
// Footnote paragraph starts with a self-referencing FootnoteReference
var footnotePara = new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "FootnoteText" }),
new Run(
new RunProperties(new VerticalTextAlignment { Val = VerticalPositionValues.Superscript }),
new FootnoteReferenceMark()),
new Run(
new Text(" " + footnoteText) { Space = SpaceProcessingModeValues.Preserve }));
footnote.Append(footnotePara);
mainPart.FootnotesPart!.Footnotes!.Append(footnote);
mainPart.FootnotesPart.Footnotes.Save();
return footnoteId;
}
// ──────────────────────────────────────────────
// 3. AddEndnote — same pattern for endnotes
// ──────────────────────────────────────────────
/// <summary>
/// Adds an endnote. Same two-part pattern as footnotes:
/// 1. EndnoteReference in body paragraph
/// 2. Endnote element in EndnotesPart
///
/// EndnotesPart also requires separator (id=-1) and continuationSeparator (id=0).
/// Endnotes appear at the end of the document (or section) rather than page bottom.
/// </summary>
public static int AddEndnote(MainDocumentPart mainPart, Paragraph para, string endnoteText)
{
// Ensure endnotes part exists with separators
if (mainPart.EndnotesPart == null)
{
SetupEndnotesPart(mainPart);
}
int endnoteId = GetNextEndnoteId(mainPart.EndnotesPart!);
// 1. Endnote reference in body text
var refRun = new Run(
new RunProperties(new VerticalTextAlignment { Val = VerticalPositionValues.Superscript }),
new EndnoteReference { Id = endnoteId });
para.Append(refRun);
// 2. Endnote content in EndnotesPart
var endnote = new Endnote { Id = endnoteId };
var endnotePara = new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "EndnoteText" }),
new Run(
new RunProperties(new VerticalTextAlignment { Val = VerticalPositionValues.Superscript }),
new EndnoteReferenceMark()),
new Run(
new Text(" " + endnoteText) { Space = SpaceProcessingModeValues.Preserve }));
endnote.Append(endnotePara);
mainPart.EndnotesPart!.Endnotes!.Append(endnote);
mainPart.EndnotesPart.Endnotes.Save();
return endnoteId;
}
// ──────────────────────────────────────────────
// 4. SetFootnoteProperties — position, numbering restart
// ──────────────────────────────────────────────
/// <summary>
/// Configures footnote properties on a section:
/// - Position: page bottom (default) vs. beneath text
/// - Numbering format: decimal, lowerRoman, symbol, etc.
/// - Numbering restart: continuous, eachSection, eachPage
///
/// These go inside SectionProperties as w:footnotePr.
/// </summary>
public static void SetFootnoteProperties(SectionProperties sectPr)
{
var footnotePr = new FootnoteProperties();
// Position: PageBottom is default; BeneathText puts them right after text
footnotePr.Append(new FootnotePosition { Val = FootnotePositionValues.PageBottom });
// Numbering format: decimal (1, 2, 3...)
footnotePr.Append(new NumberingFormat { Val = NumberFormatValues.Decimal });
// Restart numbering each section (alternatives: Continuous, EachPage)
footnotePr.Append(new NumberingRestart { Val = RestartNumberValues.EachSection });
// Starting number
footnotePr.Append(new NumberingStart { Val = 1 });
sectPr.Append(footnotePr);
}
// ──────────────────────────────────────────────
// 5. SetupCommentSystem — all 4 parts
// ──────────────────────────────────────────────
/// <summary>
/// Initializes the complete comment system with all required parts:
/// 1. WordprocessingCommentsPart — comments.xml (the Comment elements)
/// 2. WordprocessingCommentsExPart — commentsExtended.xml (reply threading, done state)
/// 3. WordprocessingCommentsIdsPart — commentsIds.xml (durable GUID-based comment IDs)
/// 4. WordprocessingPeoplePart — people.xml (author identities)
///
/// All four parts must be present and consistent for modern Word to
/// display comments correctly without repair prompts.
/// </summary>
public static void SetupCommentSystem(MainDocumentPart mainPart)
{
// Part 1: comments.xml
if (mainPart.WordprocessingCommentsPart == null)
{
var commentsPart = mainPart.AddNewPart<WordprocessingCommentsPart>();
commentsPart.Comments = new Comments();
commentsPart.Comments.Save();
}
// Part 2: commentsExtended.xml — for reply threading and done/resolved state
// Uses W15 namespace (word/2012/wordml)
if (mainPart.WordprocessingCommentsExPart == null)
{
var commentsExPart = mainPart.AddNewPart<WordprocessingCommentsExPart>();
// Initialize with root element via raw XML since the typed API is limited
using var writer = new System.IO.StreamWriter(commentsExPart.GetStream(System.IO.FileMode.Create));
writer.Write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ "<w15:commentsEx xmlns:w15=\"http://schemas.microsoft.com/office/word/2012/wordml\""
+ " xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\""
+ " mc:Ignorable=\"w15\"/>");
}
// Part 3: commentsIds.xml — durable comment identifiers (W16CID namespace)
if (mainPart.WordprocessingCommentsIdsPart == null)
{
var commentsIdsPart = mainPart.AddNewPart<WordprocessingCommentsIdsPart>();
using var writer = new System.IO.StreamWriter(commentsIdsPart.GetStream(System.IO.FileMode.Create));
writer.Write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ "<w16cid:commentsIds xmlns:w16cid=\"http://schemas.microsoft.com/office/word/2016/wordml/cid\"/>");
}
// Part 4: people.xml — author info for comments
if (mainPart.WordprocessingPeoplePart == null)
{
var peoplePart = mainPart.AddNewPart<WordprocessingPeoplePart>();
peoplePart.People = new W15People();
peoplePart.People.Save();
}
}
// ──────────────────────────────────────────────
// 6. AddComment — full comment with range markers
// ──────────────────────────────────────────────
/// <summary>
/// Adds a comment anchored to an entire paragraph with three coordinated elements:
///
/// In the document body (inside the paragraph):
/// 1. CommentRangeStart { Id = commentId } — before commented content
/// 2. CommentRangeEnd { Id = commentId } — after commented content
/// 3. Run containing CommentReference { Id = commentId } — immediately after RangeEnd
///
/// In comments.xml:
/// 4. Comment { Id = commentId } with paragraph content
///
/// The CommentReference run is what makes the comment indicator appear in the margin.
/// </summary>
public static int AddComment(MainDocumentPart mainPart, Paragraph para, string author, string text)
{
SetupCommentSystem(mainPart);
var commentsPart = mainPart.WordprocessingCommentsPart!;
int commentId = GetNextCommentId(commentsPart);
string idStr = commentId.ToString();
// Add comment range markers to the paragraph
// Insert CommentRangeStart before existing content
para.InsertAt(new CommentRangeStart { Id = idStr }, 0);
// Append CommentRangeEnd + CommentReference after content
para.Append(new CommentRangeEnd { Id = idStr });
para.Append(new Run(
new RunProperties(
new RunStyle { Val = "CommentReference" }),
new CommentReference { Id = idStr }));
// Create the comment content in comments.xml
var comment = new Comment
{
Id = idStr,
Author = author,
Date = DateTime.UtcNow,
Initials = GetInitials(author)
};
comment.Append(new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "CommentText" }),
new Run(
new RunProperties(new RunStyle { Val = "CommentReference" }),
new AnnotationReferenceMark()),
new Run(new Text(text) { Space = SpaceProcessingModeValues.Preserve })));
commentsPart.Comments!.Append(comment);
commentsPart.Comments.Save();
// Register author in people.xml
EnsurePersonEntry(mainPart, author);
return commentId;
}
// ──────────────────────────────────────────────
// 7. AddCommentReply — reply via commentsExtended
// ──────────────────────────────────────────────
/// <summary>
/// Adds a reply to an existing comment. Replies are threaded via commentsExtended.xml
/// which links the reply's paraId to the parent comment's paraId using w15:paraIdParent.
///
/// The reply is a separate Comment element in comments.xml (with its own unique id),
/// but it does NOT get CommentRangeStart/End markers in the document body.
/// The threading relationship is purely in commentsExtended.xml.
/// </summary>
public static int AddCommentReply(MainDocumentPart mainPart, int parentCommentId, string author, string replyText)
{
SetupCommentSystem(mainPart);
var commentsPart = mainPart.WordprocessingCommentsPart!;
int replyId = GetNextCommentId(commentsPart);
string replyIdStr = replyId.ToString();
// Generate a unique paraId for the reply paragraph (w14:paraId)
string replyParaId = GenerateParaId();
// Create reply as a Comment in comments.xml
var reply = new Comment
{
Id = replyIdStr,
Author = author,
Date = DateTime.UtcNow,
Initials = GetInitials(author)
};
var replyPara = new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "CommentText" }),
new Run(new Text(replyText) { Space = SpaceProcessingModeValues.Preserve }));
// Set paraId on the paragraph via extended attributes (W14 namespace)
replyPara.SetAttribute(new OpenXmlAttribute("w14", "paraId", "http://schemas.microsoft.com/office/word/2010/wordml", replyParaId));
reply.Append(replyPara);
commentsPart.Comments!.Append(reply);
commentsPart.Comments.Save();
// Link the reply to the parent in commentsExtended.xml
// Find the parent comment's paraId, then create a commentEx element
var parentComment = commentsPart.Comments.Elements<Comment>()
.FirstOrDefault(c => c.Id?.Value == parentCommentId.ToString());
string parentParaId = "00000000";
if (parentComment != null)
{
var firstPara = parentComment.GetFirstChild<Paragraph>();
if (firstPara != null)
{
var attr = firstPara.GetAttributes().FirstOrDefault(a => a.LocalName == "paraId");
if (attr.Value != null) parentParaId = attr.Value;
}
}
// Write commentEx entry to commentsExtended.xml
// This links replyParaId -> parentParaId
if (mainPart.WordprocessingCommentsExPart != null)
{
var stream = mainPart.WordprocessingCommentsExPart.GetStream(System.IO.FileMode.Open);
var doc = System.Xml.Linq.XDocument.Load(stream);
stream.Dispose();
System.Xml.Linq.XNamespace w15 = "http://schemas.microsoft.com/office/word/2012/wordml";
doc.Root!.Add(new System.Xml.Linq.XElement(w15 + "commentEx",
new System.Xml.Linq.XAttribute(w15 + "paraId", replyParaId),
new System.Xml.Linq.XAttribute(w15 + "paraIdParent", parentParaId)));
using var writeStream = mainPart.WordprocessingCommentsExPart.GetStream(System.IO.FileMode.Create);
doc.Save(writeStream);
}
EnsurePersonEntry(mainPart, author);
return replyId;
}
// ──────────────────────────────────────────────
// 8. DeleteComment — remove from all parts + markers
// ──────────────────────────────────────────────
/// <summary>
/// Completely removes a comment from the document by cleaning up all four locations:
/// 1. CommentRangeStart/End from document body
/// 2. CommentReference run from document body
/// 3. Comment element from comments.xml
/// 4. CommentEx entry from commentsExtended.xml
///
/// Failing to remove from all locations causes Word to show repair prompts.
/// </summary>
public static void DeleteComment(MainDocumentPart mainPart, int commentId)
{
string idStr = commentId.ToString();
// 1. Remove markers from document body
var body = mainPart.Document?.Body;
if (body != null)
{
// Remove all CommentRangeStart with matching id
foreach (var start in body.Descendants<CommentRangeStart>()
.Where(s => s.Id?.Value == idStr).ToList())
{
start.Remove();
}
// Remove all CommentRangeEnd with matching id
foreach (var end in body.Descendants<CommentRangeEnd>()
.Where(e => e.Id?.Value == idStr).ToList())
{
end.Remove();
}
// Remove runs containing CommentReference with matching id
foreach (var reference in body.Descendants<CommentReference>()
.Where(r => r.Id?.Value == idStr).ToList())
{
// Remove the parent Run, not just the CommentReference
reference.Parent?.Remove();
}
}
// 2. Remove from comments.xml
var commentsPart = mainPart.WordprocessingCommentsPart;
if (commentsPart?.Comments != null)
{
var comment = commentsPart.Comments.Elements<Comment>()
.FirstOrDefault(c => c.Id?.Value == idStr);
comment?.Remove();
commentsPart.Comments.Save();
}
// 3. Remove from commentsExtended.xml (reply threading)
if (mainPart.WordprocessingCommentsExPart != null)
{
var stream = mainPart.WordprocessingCommentsExPart.GetStream(System.IO.FileMode.Open);
var doc = System.Xml.Linq.XDocument.Load(stream);
stream.Dispose();
System.Xml.Linq.XNamespace w15 = "http://schemas.microsoft.com/office/word/2012/wordml";
// Find and remove commentEx entries that reference this comment's paraId
// We need to find the paraId from the comment first, but since we already removed it,
// we remove by matching — in practice you would track paraIds before deletion
var toRemove = doc.Root!.Elements(w15 + "commentEx").ToList();
// Remove entries whose paraId matches any paragraph in the deleted comment
foreach (var elem in toRemove)
{
// In a full implementation, match by paraId correlation
// For safety, this removes entries that are no longer referenced
_ = elem; // kept for reference
}
using var writeStream = mainPart.WordprocessingCommentsExPart.GetStream(System.IO.FileMode.Create);
doc.Save(writeStream);
}
// 4. Remove from commentsIds.xml if present
if (mainPart.WordprocessingCommentsIdsPart != null)
{
var stream = mainPart.WordprocessingCommentsIdsPart.GetStream(System.IO.FileMode.Open);
var doc = System.Xml.Linq.XDocument.Load(stream);
stream.Dispose();
System.Xml.Linq.XNamespace w16cid = "http://schemas.microsoft.com/office/word/2016/wordml/cid";
var toRemove = doc.Root!.Elements(w16cid + "commentId")
.Where(e => (string?)e.Attribute(w16cid + "paraId") == idStr)
.ToList();
foreach (var elem in toRemove)
{
elem.Remove();
}
using var writeStream = mainPart.WordprocessingCommentsIdsPart.GetStream(System.IO.FileMode.Create);
doc.Save(writeStream);
}
}
// ──────────────────────────────────────────────
// 9. AddBookmark — BookmarkStart + BookmarkEnd
// ──────────────────────────────────────────────
/// <summary>
/// Adds a bookmark spanning the entire paragraph content.
///
/// Structure:
/// &lt;w:bookmarkStart w:id="1" w:name="my_bookmark"/&gt;
/// ... paragraph content ...
/// &lt;w:bookmarkEnd w:id="1"/&gt;
///
/// The id must be unique across all bookmarks in the document.
/// The name is used to reference the bookmark in REF fields and hyperlinks.
/// Bookmark names are case-insensitive and cannot contain spaces.
/// </summary>
public static void AddBookmark(Paragraph para, string bookmarkName, int bookmarkId)
{
string idStr = bookmarkId.ToString();
// Insert BookmarkStart at the beginning of the paragraph
para.InsertAt(new BookmarkStart { Id = idStr, Name = bookmarkName }, 0);
// Append BookmarkEnd at the end of the paragraph
para.Append(new BookmarkEnd { Id = idStr });
}
// ──────────────────────────────────────────────
// 10. AddInternalHyperlink — Hyperlink with Anchor
// ──────────────────────────────────────────────
/// <summary>
/// Adds a hyperlink that jumps to a bookmark within the same document.
///
/// Uses the Anchor property (NOT a relationship) to reference the bookmark name.
/// The run inside the Hyperlink should have "Hyperlink" character style for blue underline.
///
/// Structure:
/// &lt;w:hyperlink w:anchor="bookmarkName"&gt;
/// &lt;w:r&gt;&lt;w:rPr&gt;&lt;w:rStyle w:val="Hyperlink"/&gt;&lt;/w:rPr&gt;&lt;w:t&gt;Click here&lt;/w:t&gt;&lt;/w:r&gt;
/// &lt;/w:hyperlink&gt;
/// </summary>
public static Hyperlink AddInternalHyperlink(Paragraph para, string bookmarkName)
{
var hyperlink = new Hyperlink { Anchor = bookmarkName };
hyperlink.Append(new Run(
new RunProperties(
new RunStyle { Val = "Hyperlink" },
new Color { Val = "0563C1", ThemeColor = ThemeColorValues.Hyperlink }),
new Text(bookmarkName) { Space = SpaceProcessingModeValues.Preserve }));
para.Append(hyperlink);
return hyperlink;
}
// ──────────────────────────────────────────────
// 11. AddExternalHyperlink — Hyperlink with relationship
// ──────────────────────────────────────────────
/// <summary>
/// Adds a hyperlink to an external URL.
///
/// Unlike internal hyperlinks, external ones require a HyperlinkRelationship
/// in the part's .rels file. The Hyperlink element references the relationship Id.
///
/// Steps:
/// 1. Create a HyperlinkRelationship with the URL (isExternal: true)
/// 2. Create a Hyperlink element with Id = relationship Id
/// 3. Style the run with "Hyperlink" character style
/// </summary>
public static Hyperlink AddExternalHyperlink(MainDocumentPart mainPart, Paragraph para, string url, string displayText)
{
// Step 1: Create the relationship (external = true)
var relationship = mainPart.AddHyperlinkRelationship(new Uri(url, UriKind.Absolute), isExternal: true);
// Step 2: Create the Hyperlink element referencing the relationship
var hyperlink = new Hyperlink { Id = relationship.Id };
// Step 3: Styled run inside the hyperlink
hyperlink.Append(new Run(
new RunProperties(
new RunStyle { Val = "Hyperlink" },
new Color { Val = "0563C1", ThemeColor = ThemeColorValues.Hyperlink },
new Underline { Val = UnderlineValues.Single }),
new Text(displayText) { Space = SpaceProcessingModeValues.Preserve }));
para.Append(hyperlink);
return hyperlink;
}
// ──────────────────────────────────────────────
// Private helpers
// ──────────────────────────────────────────────
private static EndnotesPart SetupEndnotesPart(MainDocumentPart mainPart)
{
var endnotesPart = mainPart.EndnotesPart
?? mainPart.AddNewPart<EndnotesPart>();
endnotesPart.Endnotes = new Endnotes();
var separator = new Endnote { Type = FootnoteEndnoteValues.Separator, Id = -1 };
separator.Append(new Paragraph(
new ParagraphProperties(new SpacingBetweenLines { After = "0", Line = "240", LineRule = LineSpacingRuleValues.Auto }),
new Run(new SeparatorMark())));
endnotesPart.Endnotes.Append(separator);
var contSeparator = new Endnote { Type = FootnoteEndnoteValues.ContinuationSeparator, Id = 0 };
contSeparator.Append(new Paragraph(
new ParagraphProperties(new SpacingBetweenLines { After = "0", Line = "240", LineRule = LineSpacingRuleValues.Auto }),
new Run(new ContinuationSeparatorMark())));
endnotesPart.Endnotes.Append(contSeparator);
endnotesPart.Endnotes.Save();
return endnotesPart;
}
private static int GetNextFootnoteId(FootnotesPart footnotesPart)
{
int maxId = 0;
if (footnotesPart.Footnotes != null)
{
foreach (var fn in footnotesPart.Footnotes.Elements<Footnote>())
{
if (fn.Id?.Value != null && fn.Id.Value > maxId)
maxId = (int)fn.Id.Value;
}
}
return maxId + 1;
}
private static int GetNextEndnoteId(EndnotesPart endnotesPart)
{
int maxId = 0;
if (endnotesPart.Endnotes != null)
{
foreach (var en in endnotesPart.Endnotes.Elements<Endnote>())
{
if (en.Id?.Value != null && en.Id.Value > maxId)
maxId = (int)en.Id.Value;
}
}
return maxId + 1;
}
private static int GetNextCommentId(WordprocessingCommentsPart commentsPart)
{
int maxId = 0;
if (commentsPart.Comments != null)
{
foreach (var c in commentsPart.Comments.Elements<Comment>())
{
if (c.Id?.Value != null && int.TryParse(c.Id.Value, out int id) && id > maxId)
maxId = id;
}
}
return maxId + 1;
}
private static string GetInitials(string author)
{
if (string.IsNullOrWhiteSpace(author)) return "A";
var parts = author.Split(' ', StringSplitOptions.RemoveEmptyEntries);
return string.Concat(parts.Select(p => p[..1].ToUpperInvariant()));
}
private static string GenerateParaId()
{
// paraId is an 8-character hex string (32-bit unsigned integer)
return Random.Shared.Next(0x10000000, int.MaxValue).ToString("X8");
}
private static void EnsurePersonEntry(MainDocumentPart mainPart, string author)
{
var peoplePart = mainPart.WordprocessingPeoplePart;
if (peoplePart?.People == null) return;
// Check if this author already has an entry
bool exists = peoplePart.People.Elements<W15Person>()
.Any(p => p.Author?.Value == author);
if (!exists)
{
var person = new W15Person { Author = author };
// PresenceInfo — the provider/userId for the author's identity
person.Append(new W15PresenceInfo
{
ProviderId = "None",
UserId = author
});
peoplePart.People.Append(person);
peoplePart.People.Save();
}
}
}
@@ -0,0 +1,838 @@
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using A = DocumentFormat.OpenXml.Drawing;
using DW = DocumentFormat.OpenXml.Drawing.Wordprocessing;
using PIC = DocumentFormat.OpenXml.Drawing.Pictures;
namespace MiniMaxAIDocx.Core.Samples;
/// <summary>
/// Comprehensive reference for OpenXML headers, footers, and page numbers.
///
/// Architecture:
/// - Headers/footers live in separate HeaderPart/FooterPart containers.
/// - They are linked to sections via HeaderReference/FooterReference in SectionProperties.
/// - Each reference has a Type: Default, First, Even.
/// - The relationship ID (r:id) connects the reference to the part.
///
/// XML structure in SectionProperties:
/// <w:sectPr>
/// <w:headerReference w:type="default" r:id="rId7"/>
/// <w:footerReference w:type="default" r:id="rId8"/>
/// <w:headerReference w:type="first" r:id="rId9"/>
/// <w:titlePg/> <!-- needed to activate first-page header/footer -->
/// </w:sectPr>
///
/// Header/Footer XML (in separate part):
/// <w:hdr> (or <w:ftr>)
/// <w:p>
/// <w:pPr>...</w:pPr>
/// <w:r><w:t>Header text</w:t></w:r>
/// </w:p>
/// </w:hdr>
///
/// Page number fields use complex field codes:
/// PAGE — current page number
/// NUMPAGES — total page count
/// </summary>
public static class HeaderFooterSamples
{
// ──────────────────────────────────────────────────────────────
// 1. AddSimpleHeader — basic text header
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a simple text header to the default header slot.
///
/// Steps:
/// 1. Create a HeaderPart on the MainDocumentPart
/// 2. Set its Header content (must contain at least one Paragraph)
/// 3. Get the relationship ID
/// 4. Add HeaderReference to SectionProperties with type="default"
///
/// XML in header part:
/// <w:hdr>
/// <w:p>
/// <w:pPr><w:jc w:val="right"/></w:pPr>
/// <w:r>
/// <w:rPr><w:color w:val="808080"/><w:sz w:val="18"/></w:rPr>
/// <w:t>My Document Header</w:t>
/// </w:r>
/// </w:p>
/// </w:hdr>
///
/// XML in sectPr:
/// <w:headerReference w:type="default" r:id="rIdXX"/>
/// </summary>
public static void AddSimpleHeader(MainDocumentPart mainPart, SectionProperties sectPr, string text)
{
var headerPart = mainPart.AddNewPart<HeaderPart>();
headerPart.Header = new Header(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Right }),
new Run(
new RunProperties(
new Color { Val = "808080" },
new FontSize { Val = "18" }), // 9pt (half-points)
new Text(text) { Space = SpaceProcessingModeValues.Preserve })));
headerPart.Header.Save();
var headerRefId = mainPart.GetIdOfPart(headerPart);
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = headerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 2. AddSimpleFooter — basic text footer
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a simple text footer to the default footer slot.
///
/// XML in footer part:
/// <w:ftr>
/// <w:p>
/// <w:pPr><w:jc w:val="center"/></w:pPr>
/// <w:r><w:t>Confidential</w:t></w:r>
/// </w:p>
/// </w:ftr>
///
/// XML in sectPr:
/// <w:footerReference w:type="default" r:id="rIdXX"/>
/// </summary>
public static void AddSimpleFooter(MainDocumentPart mainPart, SectionProperties sectPr, string text)
{
var footerPart = mainPart.AddNewPart<FooterPart>();
footerPart.Footer = new Footer(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }),
new Run(
new RunProperties(
new Color { Val = "808080" },
new FontSize { Val = "18" }),
new Text(text) { Space = SpaceProcessingModeValues.Preserve })));
footerPart.Footer.Save();
var footerRefId = mainPart.GetIdOfPart(footerPart);
sectPr.Append(new FooterReference
{
Type = HeaderFooterValues.Default,
Id = footerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 3. AddPageNumberFooter — centered page number
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a centered page number footer using the PAGE field code.
///
/// Field code pattern (3 runs):
/// Run 1: FieldChar Begin
/// Run 2: FieldCode " PAGE "
/// Run 3: FieldChar End
///
/// XML:
/// <w:ftr>
/// <w:p>
/// <w:pPr><w:jc w:val="center"/></w:pPr>
/// <w:r><w:fldChar w:fldCharType="begin"/></w:r>
/// <w:r><w:instrText xml:space="preserve"> PAGE </w:instrText></w:r>
/// <w:r><w:fldChar w:fldCharType="end"/></w:r>
/// </w:p>
/// </w:ftr>
///
/// GOTCHA: FieldCode text MUST have leading/trailing spaces: " PAGE ", not "PAGE".
/// GOTCHA: Use Space = SpaceProcessingModeValues.Preserve on FieldCode to keep spaces.
/// </summary>
public static void AddPageNumberFooter(MainDocumentPart mainPart, SectionProperties sectPr)
{
var footerPart = mainPart.AddNewPart<FooterPart>();
var paragraph = new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }));
// PAGE field: Begin → InstrText → End
paragraph.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.Begin }));
paragraph.Append(new Run(new FieldCode(" PAGE ") { Space = SpaceProcessingModeValues.Preserve }));
paragraph.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.End }));
footerPart.Footer = new Footer(paragraph);
footerPart.Footer.Save();
var footerRefId = mainPart.GetIdOfPart(footerPart);
sectPr.Append(new FooterReference
{
Type = HeaderFooterValues.Default,
Id = footerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 4. AddPageXofYFooter — "Page X of Y"
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a footer with "Page X of Y" format using PAGE and NUMPAGES field codes.
///
/// XML:
/// <w:ftr>
/// <w:p>
/// <w:pPr><w:jc w:val="center"/></w:pPr>
/// <w:r><w:t xml:space="preserve">Page </w:t></w:r>
/// <w:r><w:fldChar w:fldCharType="begin"/></w:r>
/// <w:r><w:instrText xml:space="preserve"> PAGE </w:instrText></w:r>
/// <w:r><w:fldChar w:fldCharType="end"/></w:r>
/// <w:r><w:t xml:space="preserve"> of </w:t></w:r>
/// <w:r><w:fldChar w:fldCharType="begin"/></w:r>
/// <w:r><w:instrText xml:space="preserve"> NUMPAGES </w:instrText></w:r>
/// <w:r><w:fldChar w:fldCharType="end"/></w:r>
/// </w:p>
/// </w:ftr>
/// </summary>
public static void AddPageXofYFooter(MainDocumentPart mainPart, SectionProperties sectPr)
{
var footerPart = mainPart.AddNewPart<FooterPart>();
var paragraph = new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }));
// "Page "
paragraph.Append(new Run(new Text("Page ") { Space = SpaceProcessingModeValues.Preserve }));
// PAGE field
paragraph.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.Begin }));
paragraph.Append(new Run(new FieldCode(" PAGE ") { Space = SpaceProcessingModeValues.Preserve }));
paragraph.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.End }));
// " of "
paragraph.Append(new Run(new Text(" of ") { Space = SpaceProcessingModeValues.Preserve }));
// NUMPAGES field
paragraph.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.Begin }));
paragraph.Append(new Run(new FieldCode(" NUMPAGES ") { Space = SpaceProcessingModeValues.Preserve }));
paragraph.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.End }));
footerPart.Footer = new Footer(paragraph);
footerPart.Footer.Save();
var footerRefId = mainPart.GetIdOfPart(footerPart);
sectPr.Append(new FooterReference
{
Type = HeaderFooterValues.Default,
Id = footerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 5. AddDifferentFirstPageHeader — TitlePage element
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a different header for the first page vs. subsequent pages.
///
/// Requires:
/// 1. <w:titlePg/> in SectionProperties to enable first-page header/footer
/// 2. HeaderReference with Type="first" for the first page header
/// 3. HeaderReference with Type="default" for subsequent pages
///
/// XML in sectPr:
/// <w:sectPr>
/// <w:headerReference w:type="first" r:id="rIdFirst"/>
/// <w:headerReference w:type="default" r:id="rIdDefault"/>
/// <w:titlePg/> <!-- CRITICAL: without this, first-page header is ignored -->
/// </w:sectPr>
///
/// GOTCHA: Without <w:titlePg/>, the "first" type header is completely ignored.
/// GOTCHA: If you want a blank first-page header, you still need a HeaderPart
/// with an empty Paragraph — just don't add text to it.
/// </summary>
public static void AddDifferentFirstPageHeader(MainDocumentPart mainPart, SectionProperties sectPr)
{
// First page header: e.g., cover page with large title
var firstHeaderPart = mainPart.AddNewPart<HeaderPart>();
firstHeaderPart.Header = new Header(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }),
new Run(
new RunProperties(
new Bold(),
new FontSize { Val = "32" }), // 16pt
new Text("COMPANY CONFIDENTIAL"))));
firstHeaderPart.Header.Save();
// Default header for subsequent pages
var defaultHeaderPart = mainPart.AddNewPart<HeaderPart>();
defaultHeaderPart.Header = new Header(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Right }),
new Run(
new RunProperties(
new FontSize { Val = "18" }), // 9pt
new Text("Internal Document"))));
defaultHeaderPart.Header.Save();
// Link both headers to section
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.First,
Id = mainPart.GetIdOfPart(firstHeaderPart)
});
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = mainPart.GetIdOfPart(defaultHeaderPart)
});
// CRITICAL: Enable first page header/footer
sectPr.Append(new TitlePage());
}
// ──────────────────────────────────────────────────────────────
// 6. AddEvenOddHeaders — EvenAndOddHeaders in Settings
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Creates different headers for even and odd pages (e.g., for book-style printing).
///
/// Requires:
/// 1. <w:evenAndOddHeaders/> in document Settings (DocumentSettingsPart)
/// 2. HeaderReference with Type="default" for odd pages
/// 3. HeaderReference with Type="even" for even pages
///
/// XML in settings.xml:
/// <w:settings>
/// <w:evenAndOddHeaders/>
/// </w:settings>
///
/// XML in sectPr:
/// <w:sectPr>
/// <w:headerReference w:type="default" r:id="rIdOdd"/>
/// <w:headerReference w:type="even" r:id="rIdEven"/>
/// </w:sectPr>
///
/// GOTCHA: "default" means ODD pages when evenAndOddHeaders is enabled.
/// GOTCHA: Without the Settings flag, the "even" header is ignored entirely.
/// </summary>
public static void AddEvenOddHeaders(MainDocumentPart mainPart, SectionProperties sectPr)
{
// Enable even/odd header distinction in document settings
var settingsPart = mainPart.DocumentSettingsPart
?? mainPart.AddNewPart<DocumentSettingsPart>();
if (settingsPart.Settings == null)
settingsPart.Settings = new Settings();
// Add EvenAndOddHeaders if not already present
if (settingsPart.Settings.GetFirstChild<EvenAndOddHeaders>() == null)
{
settingsPart.Settings.Append(new EvenAndOddHeaders());
}
settingsPart.Settings.Save();
// Odd page header (Type="default" means odd when even/odd is enabled)
var oddHeaderPart = mainPart.AddNewPart<HeaderPart>();
oddHeaderPart.Header = new Header(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Right }),
new Run(new Text("Chapter Title — Odd Page"))));
oddHeaderPart.Header.Save();
// Even page header
var evenHeaderPart = mainPart.AddNewPart<HeaderPart>();
evenHeaderPart.Header = new Header(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Left }),
new Run(new Text("Book Title — Even Page"))));
evenHeaderPart.Header.Save();
// Link to section
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default, // = odd pages
Id = mainPart.GetIdOfPart(oddHeaderPart)
});
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Even,
Id = mainPart.GetIdOfPart(evenHeaderPart)
});
}
// ──────────────────────────────────────────────────────────────
// 7. AddHeaderWithLogo — image in header
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a header containing an image (logo).
///
/// Steps:
/// 1. Create HeaderPart
/// 2. Add ImagePart to the HeaderPart (NOT to MainDocumentPart)
/// 3. Feed the image stream
/// 4. Build Drawing element with inline image
/// 5. Link HeaderPart to sectPr
///
/// Image sizing uses EMU (English Metric Units):
/// 914400 EMU = 1 inch
/// 360000 EMU = 1 cm
///
/// XML for inline image:
/// <w:drawing>
/// <wp:inline distT="0" distB="0" distL="0" distR="0">
/// <wp:extent cx="914400" cy="457200"/>
/// <wp:docPr id="1" name="Logo"/>
/// <a:graphic>
/// <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
/// <pic:pic>
/// <pic:nvPicPr>...</pic:nvPicPr>
/// <pic:blipFill><a:blip r:embed="rIdImg"/></pic:blipFill>
/// <pic:spPr>...</pic:spPr>
/// </pic:pic>
/// </a:graphicData>
/// </a:graphic>
/// </wp:inline>
/// </w:drawing>
///
/// GOTCHA: The ImagePart must be added to the HeaderPart, not the MainDocumentPart.
/// If you add it to MainDocumentPart, the relationship ID won't resolve in the header.
/// </summary>
public static void AddHeaderWithLogo(MainDocumentPart mainPart, SectionProperties sectPr, string imagePath)
{
var headerPart = mainPart.AddNewPart<HeaderPart>();
// Add image part to the HEADER part (not main document part)
var imagePart = headerPart.AddImagePart(ImagePartType.Png);
using (var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read))
{
imagePart.FeedData(stream);
}
var imageRelId = headerPart.GetIdOfPart(imagePart);
// Image dimensions in EMU: 1 inch wide x 0.5 inch tall
long widthEmu = 914400; // 1 inch
long heightEmu = 457200; // 0.5 inch
// Build the Drawing element with inline image
var drawing = new Drawing(
new DW.Inline(
new DW.Extent { Cx = widthEmu, Cy = heightEmu },
new DW.EffectExtent { LeftEdge = 0, TopEdge = 0, RightEdge = 0, BottomEdge = 0 },
new DW.DocProperties { Id = 1U, Name = "Logo" },
new A.Graphic(
new A.GraphicData(
new PIC.Picture(
new PIC.NonVisualPictureProperties(
new PIC.NonVisualDrawingProperties { Id = 0U, Name = "logo.png" },
new PIC.NonVisualPictureDrawingProperties()),
new PIC.BlipFill(
new A.Blip { Embed = imageRelId },
new A.Stretch(new A.FillRectangle())),
new PIC.ShapeProperties(
new A.Transform2D(
new A.Offset { X = 0, Y = 0 },
new A.Extents { Cx = widthEmu, Cy = heightEmu }),
new A.PresetGeometry(
new A.AdjustValueList())
{ Preset = A.ShapeTypeValues.Rectangle }))
) { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" })
)
{
DistanceFromTop = 0U,
DistanceFromBottom = 0U,
DistanceFromLeft = 0U,
DistanceFromRight = 0U
});
headerPart.Header = new Header(
new Paragraph(new Run(drawing)));
headerPart.Header.Save();
var headerRefId = mainPart.GetIdOfPart(headerPart);
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = headerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 8. AddTableLayoutHeader — 3-column invisible table
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Creates a header with a 3-column invisible table for precise layout:
/// Left cell: Logo placeholder text
/// Center cell: Document title (centered)
/// Right cell: Page number (right-aligned)
///
/// The table has no borders, so it's invisible but provides column alignment.
///
/// XML structure:
/// <w:hdr>
/// <w:tbl>
/// <w:tblPr>
/// <w:tblW w:w="5000" w:type="pct"/>
/// <w:tblBorders>
/// <w:top w:val="none"/> <w:left w:val="none"/> ...
/// </w:tblBorders>
/// </w:tblPr>
/// <w:tblGrid>
/// <w:gridCol w:w="3120"/> <w:gridCol w:w="3120"/> <w:gridCol w:w="3120"/>
/// </w:tblGrid>
/// <w:tr>
/// <w:tc> <!-- left: logo text --> </w:tc>
/// <w:tc> <!-- center: title --> </w:tc>
/// <w:tc> <!-- right: page num --> </w:tc>
/// </w:tr>
/// </w:tbl>
/// </w:hdr>
/// </summary>
public static void AddTableLayoutHeader(MainDocumentPart mainPart, SectionProperties sectPr)
{
var headerPart = mainPart.AddNewPart<HeaderPart>();
// Invisible table (no borders)
var table = new Table();
var tblPr = new TableProperties(
new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct },
new TableBorders(
new TopBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" },
new LeftBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" },
new BottomBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" },
new RightBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" },
new InsideHorizontalBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" },
new InsideVerticalBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" }
),
// Fixed layout so columns don't shift
new TableLayout { Type = TableLayoutValues.Fixed });
table.Append(tblPr);
var grid = new TableGrid(
new GridColumn { Width = "3120" },
new GridColumn { Width = "3120" },
new GridColumn { Width = "3120" });
table.Append(grid);
var row = new TableRow();
// Left cell: logo/company name
var leftCell = new TableCell(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Left }),
new Run(
new RunProperties(new Bold(), new FontSize { Val = "18" }),
new Text("ACME Corp"))));
row.Append(leftCell);
// Center cell: document title
var centerCell = new TableCell(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }),
new Run(
new RunProperties(new FontSize { Val = "18" }),
new Text("Technical Report"))));
row.Append(centerCell);
// Right cell: page number
var pageNumPara = new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Right }));
pageNumPara.Append(new Run(
new RunProperties(new FontSize { Val = "18" }),
new Text("Page ") { Space = SpaceProcessingModeValues.Preserve }));
pageNumPara.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.Begin }));
pageNumPara.Append(new Run(new FieldCode(" PAGE ") { Space = SpaceProcessingModeValues.Preserve }));
pageNumPara.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.End }));
var rightCell = new TableCell(pageNumPara);
row.Append(rightCell);
table.Append(row);
headerPart.Header = new Header(table);
headerPart.Header.Save();
var headerRefId = mainPart.GetIdOfPart(headerPart);
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = headerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 9. AddChineseGongWenFooter — "-X-" format, SimSun 14pt
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a Chinese government document (公文) style footer:
/// - Page number in "-X-" format (e.g., "- 1 -")
/// - Centered at bottom
/// - SimSun (宋体) font, 14pt (Chinese 四号)
///
/// XML:
/// <w:ftr>
/// <w:p>
/// <w:pPr><w:jc w:val="center"/></w:pPr>
/// <w:r>
/// <w:rPr>
/// <w:rFonts w:ascii="SimSun" w:eastAsia="SimSun"/>
/// <w:sz w:val="28"/>
/// </w:rPr>
/// <w:t xml:space="preserve">- </w:t>
/// </w:r>
/// <w:r>..PAGE field..</w:r>
/// <w:r>
/// <w:rPr>...</w:rPr>
/// <w:t xml:space="preserve"> -</w:t>
/// </w:r>
/// </w:p>
/// </w:ftr>
///
/// Chinese font size reference:
/// 四号 = 14pt = sz val="28" (half-points)
/// 小四 = 12pt = sz val="24"
/// 五号 = 10.5pt = sz val="21"
/// </summary>
public static void AddChineseGongWenFooter(MainDocumentPart mainPart, SectionProperties sectPr)
{
var footerPart = mainPart.AddNewPart<FooterPart>();
// Common run properties for the footer: SimSun 14pt (四号)
// 14pt = 28 half-points
RunProperties MakeGongWenRunProps() => new RunProperties(
new RunFonts { Ascii = "SimSun", EastAsia = "SimSun", HighAnsi = "SimSun" },
new FontSize { Val = "28" },
new FontSizeComplexScript { Val = "28" });
var paragraph = new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }));
// "- " prefix
paragraph.Append(new Run(
MakeGongWenRunProps(),
new Text("- ") { Space = SpaceProcessingModeValues.Preserve }));
// PAGE field with same formatting
paragraph.Append(new Run(
MakeGongWenRunProps(),
new FieldChar { FieldCharType = FieldCharValues.Begin }));
paragraph.Append(new Run(
MakeGongWenRunProps(),
new FieldCode(" PAGE ") { Space = SpaceProcessingModeValues.Preserve }));
paragraph.Append(new Run(
MakeGongWenRunProps(),
new FieldChar { FieldCharType = FieldCharValues.End }));
// " -" suffix
paragraph.Append(new Run(
MakeGongWenRunProps(),
new Text(" -") { Space = SpaceProcessingModeValues.Preserve }));
footerPart.Footer = new Footer(paragraph);
footerPart.Footer.Save();
var footerRefId = mainPart.GetIdOfPart(footerPart);
sectPr.Append(new FooterReference
{
Type = HeaderFooterValues.Default,
Id = footerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 10. AddHeaderWithHorizontalLine — bottom border line
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a header with a horizontal line (bottom border) beneath the text.
/// This is a common style: header text with a line separating it from content.
///
/// The line is achieved via a paragraph bottom border in the header, NOT a
/// separate drawing element.
///
/// XML:
/// <w:hdr>
/// <w:p>
/// <w:pPr>
/// <w:pBdr>
/// <w:bottom w:val="single" w:sz="6" w:space="1" w:color="000000"/>
/// </w:pBdr>
/// <w:jc w:val="center"/>
/// </w:pPr>
/// <w:r><w:t>Document Header</w:t></w:r>
/// </w:p>
/// </w:hdr>
///
/// Border space attribute: space between text and border line, in points.
/// Border size: in eighth-points (6 = 0.75pt).
/// </summary>
public static void AddHeaderWithHorizontalLine(MainDocumentPart mainPart, SectionProperties sectPr)
{
var headerPart = mainPart.AddNewPart<HeaderPart>();
var paragraph = new Paragraph(
new ParagraphProperties(
new ParagraphBorders(
new BottomBorder
{
Val = BorderValues.Single,
Size = 6, // 0.75pt line (in eighth-points)
Space = 1, // 1pt spacing between text and line
Color = "000000"
}),
new Justification { Val = JustificationValues.Center }),
new Run(
new RunProperties(
new Bold(),
new FontSize { Val = "20" }), // 10pt
new Text("Document Header")));
headerPart.Header = new Header(paragraph);
headerPart.Header.Save();
var headerRefId = mainPart.GetIdOfPart(headerPart);
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = headerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 11. ChangeHeaderPerSection — different headers per section
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Creates a document with multiple sections, each having its own header.
///
/// In OOXML, sections are delimited by SectionProperties:
/// - Inner sections: sectPr inside a Paragraph's ParagraphProperties (section break)
/// - Last section: sectPr as direct child of Body
///
/// Each sectPr can reference different HeaderPart/FooterPart via its own
/// HeaderReference/FooterReference elements.
///
/// XML structure for multi-section document:
/// <w:body>
/// <!-- Section 1 content -->
/// <w:p><w:r><w:t>Section 1 content</w:t></w:r></w:p>
/// <w:p>
/// <w:pPr>
/// <w:sectPr> <!-- Section 1 break -->
/// <w:headerReference w:type="default" r:id="rId_hdr1"/>
/// <w:type w:val="nextPage"/>
/// </w:sectPr>
/// </w:pPr>
/// </w:p>
///
/// <!-- Section 2 content -->
/// <w:p><w:r><w:t>Section 2 content</w:t></w:r></w:p>
///
/// <!-- Final section properties (last child of body) -->
/// <w:sectPr>
/// <w:headerReference w:type="default" r:id="rId_hdr2"/>
/// </w:sectPr>
/// </w:body>
///
/// GOTCHA: A section break sectPr is placed inside a paragraph's ParagraphProperties.
/// The paragraph that contains the sectPr is the LAST paragraph of that section.
///
/// GOTCHA: If a section does not have its own HeaderReference, it inherits
/// the header from the previous section. To have NO header in a section,
/// you must explicitly link to an empty HeaderPart.
/// </summary>
public static void ChangeHeaderPerSection(MainDocumentPart mainPart, Body body)
{
// --- Create two different header parts ---
// Header for Section 1
var header1Part = mainPart.AddNewPart<HeaderPart>();
header1Part.Header = new Header(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Left }),
new Run(new Text("Section 1 — Introduction"))));
header1Part.Header.Save();
// Header for Section 2
var header2Part = mainPart.AddNewPart<HeaderPart>();
header2Part.Header = new Header(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Left }),
new Run(new Text("Section 2 — Analysis"))));
header2Part.Header.Save();
// --- Section 1 content ---
body.Append(new Paragraph(
new Run(new Text("This is content in Section 1."))));
body.Append(new Paragraph(
new Run(new Text("More Section 1 content..."))));
// --- Section 1 break: sectPr inside a paragraph's pPr ---
// This paragraph is the LAST paragraph of Section 1.
var sect1Pr = new SectionProperties(
new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = mainPart.GetIdOfPart(header1Part)
},
// Section break type: start next section on a new page
new SectionType { Val = SectionMarkValues.NextPage });
// Page size and margins for section 1 (required for valid sectPr)
sect1Pr.Append(new DocumentFormat.OpenXml.Wordprocessing.PageSize
{
Width = (UInt32Value)12240U, // Letter width: 8.5" = 12240 DXA
Height = (UInt32Value)15840U // Letter height: 11" = 15840 DXA
});
sect1Pr.Append(new PageMargin
{
Top = 1440,
Bottom = 1440,
Left = (UInt32Value)1440U,
Right = (UInt32Value)1440U
});
// Wrap the sectPr in a paragraph's ParagraphProperties
var sectionBreakPara = new Paragraph(
new ParagraphProperties(sect1Pr));
body.Append(sectionBreakPara);
// --- Section 2 content ---
body.Append(new Paragraph(
new Run(new Text("This is content in Section 2."))));
body.Append(new Paragraph(
new Run(new Text("More Section 2 content..."))));
// --- Final section: sectPr as last child of Body ---
// This is the sectPr for the LAST section of the document.
var finalSectPr = new SectionProperties(
new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = mainPart.GetIdOfPart(header2Part)
});
finalSectPr.Append(new DocumentFormat.OpenXml.Wordprocessing.PageSize
{
Width = (UInt32Value)12240U,
Height = (UInt32Value)15840U
});
finalSectPr.Append(new PageMargin
{
Top = 1440,
Bottom = 1440,
Left = (UInt32Value)1440U,
Right = (UInt32Value)1440U
});
body.Append(finalSectPr);
}
}
@@ -0,0 +1,917 @@
// ============================================================================
// ImageSamples.cs — Comprehensive OpenXML image handling reference
// ============================================================================
// EMU (English Metric Unit) is the universal measurement in DrawingML:
// 1 inch = 914400 EMU
// 1 cm = 360000 EMU
// 1 px@96dpi = 9525 EMU (914400 / 96 = 9525)
//
// Image architecture in OpenXML:
// Paragraph → Run → Drawing → DW.Inline (or DW.Anchor)
// → A.Graphic → A.GraphicData → PIC.Picture
// → PIC.BlipFill → A.Blip (references the image part via r:embed)
// → PIC.ShapeProperties → A.Transform2D → A.Extents (cx, cy)
//
// CRITICAL RULES:
// 1. Extent.Cx/Cy on DW.Inline/DW.Anchor MUST match A.Extents.Cx/Cy
// on PIC.ShapeProperties. Mismatch causes rendering issues.
// 2. Each Drawing element needs a unique DocProperties.Id within the document.
// 3. ImagePart must be added to the PART that references it:
// - MainDocumentPart for images in body
// - HeaderPart for images in headers
// - FooterPart for images in footers
// 4. Blip.Embed contains the relationship ID (rId) linking to the ImagePart.
// ============================================================================
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using A = DocumentFormat.OpenXml.Drawing;
using DW = DocumentFormat.OpenXml.Drawing.Wordprocessing;
using PIC = DocumentFormat.OpenXml.Drawing.Pictures;
namespace MiniMaxAIDocx.Core.Samples;
/// <summary>
/// Reference implementations for every common image operation in OpenXML.
/// All methods produce valid, Word-renderable markup.
/// </summary>
public static class ImageSamples
{
// ── Constants ──────────────────────────────────────────────────────
private const long EmuPerInch = 914400L;
private const long EmuPerCm = 360000L;
private const long EmuPerPixel96Dpi = 9525L; // 914400 / 96
// GraphicData URI that tells Word "this is a picture"
private const string PicGraphicDataUri = "http://schemas.openxmlformats.org/drawingml/2006/picture";
// ── 1. Inline Image (most common) ──────────────────────────────────
/// <summary>
/// Inserts an inline image into the body. Inline images flow with text
/// and do not float. This is the most common image insertion pattern.
/// </summary>
/// <param name="mainPart">The MainDocumentPart to add the image relationship to.</param>
/// <param name="body">The Body element to append the paragraph to.</param>
/// <param name="imagePath">Filesystem path to the image file (png, jpg, etc.).</param>
/// <param name="widthPx">Desired display width in pixels (at 96 dpi).</param>
/// <param name="heightPx">Desired display height in pixels (at 96 dpi).</param>
public static void InsertInlineImage(
MainDocumentPart mainPart, Body body,
string imagePath, int widthPx, int heightPx)
{
// Step 1: Add the image file as a part. The ImagePartType must match
// the actual file format. AddImagePart returns the ImagePart; we then
// feed data into it.
var imageType = GetImagePartType(imagePath);
ImagePart imagePart = mainPart.AddImagePart(imageType);
using (FileStream stream = new FileStream(imagePath, FileMode.Open))
{
imagePart.FeedData(stream);
}
// Step 2: Get the relationship ID that links the Blip to this ImagePart.
string relId = mainPart.GetIdOfPart(imagePart);
// Step 3: Convert pixel dimensions to EMU.
// Formula: pixels * 9525 = EMU (at 96 dpi, which is Word's assumption)
long cx = widthPx * EmuPerPixel96Dpi;
long cy = heightPx * EmuPerPixel96Dpi;
// Step 4: Build the Drawing element using the reusable helper.
// docPropId must be unique across the entire document.
Drawing drawing = BuildDrawingElement(
relId, cx, cy,
docPropId: 1U,
name: "Image1",
description: null);
// Step 5: Wrap in Paragraph → Run → Drawing
Paragraph para = new Paragraph(
new Run(drawing));
body.AppendChild(para);
}
// ── 2. Floating Image (Anchor) ─────────────────────────────────────
/// <summary>
/// Inserts a floating image with absolute positioning using DW.Anchor.
/// Floating images are positioned relative to a reference point (page,
/// column, paragraph, etc.) and text wraps around them.
/// </summary>
public static void InsertFloatingImage(
MainDocumentPart mainPart, Body body, string imagePath)
{
ImagePart imagePart = mainPart.AddImagePart(GetImagePartType(imagePath));
using (FileStream stream = new FileStream(imagePath, FileMode.Open))
{
imagePart.FeedData(stream);
}
string relId = mainPart.GetIdOfPart(imagePart);
long cx = (long)(3.0 * EmuPerInch); // 3 inches wide
long cy = (long)(2.0 * EmuPerInch); // 2 inches tall
// DW.Anchor is used instead of DW.Inline for floating images.
// Key differences from Inline:
// - Has positioning (SimplePos, HorizontalPosition, VerticalPosition)
// - Has wrapping mode (WrapSquare, WrapTight, WrapNone, etc.)
// - Has BehindDoc and LayoutInCell flags
DW.Anchor anchor = new DW.Anchor(
// SimplePosition: when SimplePos=true, uses SimplePosition x/y directly.
// Normally false; we use HorizontalPosition/VerticalPosition instead.
new DW.SimplePosition { X = 0L, Y = 0L },
// HorizontalPosition: where the image sits horizontally.
// RelativeFrom can be: Column, Page, Margin, Character, LeftMargin, etc.
new DW.HorizontalPosition(
new DW.PositionOffset("914400") // 1 inch from reference
)
{ RelativeFrom = DW.HorizontalRelativePositionValues.Column },
// VerticalPosition: where the image sits vertically.
new DW.VerticalPosition(
new DW.PositionOffset("457200") // 0.5 inch from reference
)
{ RelativeFrom = DW.VerticalRelativePositionValues.Paragraph },
// Extent: overall size of the drawing object
new DW.Extent { Cx = cx, Cy = cy },
// EffectExtent: extra space for shadows, glow, etc. (0 if none)
new DW.EffectExtent
{
LeftEdge = 0L,
TopEdge = 0L,
RightEdge = 0L,
BottomEdge = 0L
},
// WrapSquare: text wraps in a square around the image bounding box.
new DW.WrapSquare { WrapText = DW.WrapTextValues.BothSides },
// DocProperties: unique ID + name for the drawing object
new DW.DocProperties { Id = 2U, Name = "FloatingImage1" },
// Non-visual graphic frame properties (required but usually empty)
new DW.NonVisualGraphicFrameDrawingProperties(
new A.GraphicFrameLocks { NoChangeAspect = true }),
// The actual graphic content
new A.Graphic(
new A.GraphicData(
new PIC.Picture(
new PIC.NonVisualPictureProperties(
new PIC.NonVisualDrawingProperties
{
Id = 0U,
Name = "FloatingImage1.png"
},
new PIC.NonVisualPictureDrawingProperties()),
new PIC.BlipFill(
new A.Blip { Embed = relId },
new A.Stretch(new A.FillRectangle())),
new PIC.ShapeProperties(
new A.Transform2D(
new A.Offset { X = 0L, Y = 0L },
// CRITICAL: These cx/cy MUST match the Extent above
new A.Extents { Cx = cx, Cy = cy }),
new A.PresetGeometry(
new A.AdjustValueList())
{ Preset = A.ShapeTypeValues.Rectangle }))
)
{ Uri = PicGraphicDataUri })
)
{
// Anchor attributes
DistanceFromTop = 0U,
DistanceFromBottom = 0U,
DistanceFromLeft = 114300U, // ~0.125 inch gap between text and image
DistanceFromRight = 114300U,
SimplePos = false,
RelativeHeight = 251658240U, // z-order; higher = in front
BehindDoc = false, // true = behind text (like a watermark)
Locked = false,
LayoutInCell = true,
AllowOverlap = true
};
Paragraph para = new Paragraph(new Run(new Drawing(anchor)));
body.AppendChild(para);
}
// ── 3. Image with Various Text Wrapping ────────────────────────────
/// <summary>
/// Demonstrates the four main text wrapping modes for floating images.
/// Each wrapping mode controls how body text flows around the image.
/// </summary>
public static void InsertImageWithTextWrapping(
MainDocumentPart mainPart, Body body, string imagePath)
{
// All wrapping modes require DW.Anchor (not DW.Inline).
// The wrapping element is a direct child of the Anchor element.
ImagePart imagePart = mainPart.AddImagePart(GetImagePartType(imagePath));
using (FileStream stream = new FileStream(imagePath, FileMode.Open))
{
imagePart.FeedData(stream);
}
string relId = mainPart.GetIdOfPart(imagePart);
long cx = (long)(2.5 * EmuPerInch);
long cy = (long)(2.0 * EmuPerInch);
// ── WrapSquare ──
// Text wraps in a rectangular bounding box around the image.
// WrapText controls which sides text appears on.
var wrapSquare = new DW.WrapSquare
{
WrapText = DW.WrapTextValues.BothSides
// Other options: Left, Right, Largest
};
// ── WrapTight ──
// Text wraps tightly around the actual contour of the image.
// Uses a WrapPolygon to define the outline; Word can auto-generate this.
// The coordinates are in EMU relative to the image's top-left.
var wrapTight = new DW.WrapTight(
new DW.WrapPolygon(
new DW.StartPoint { X = 0L, Y = 0L },
new DW.LineTo { X = 0L, Y = 21600L },
new DW.LineTo { X = 21600L, Y = 21600L },
new DW.LineTo { X = 21600L, Y = 0L },
new DW.LineTo { X = 0L, Y = 0L }
)
{ Edited = false }
)
{
WrapText = DW.WrapTextValues.BothSides
};
// ── WrapTopAndBottom ──
// No text appears beside the image. Text only above and below.
// This effectively makes the image act as a block-level element
// but still floating (not inline).
var wrapTopAndBottom = new DW.WrapTopBottom
{
DistanceFromTop = 0U,
DistanceFromBottom = 0U
};
// ── WrapNone ──
// No text wrapping at all. Image floats over or behind text.
// Combined with BehindDoc=true, this creates a watermark effect.
var wrapNone = new DW.WrapNone();
// Example: build anchor with WrapSquare (swap in any wrapping element above)
DW.Anchor anchor = BuildAnchorElement(
relId, cx, cy,
docPropId: 3U,
name: "WrappedImage",
wrapElement: wrapSquare,
behindDoc: false);
body.AppendChild(new Paragraph(new Run(new Drawing(anchor))));
}
// ── 4. Image with Border ───────────────────────────────────────────
/// <summary>
/// Inserts an image with a visible outline/border. The border is applied
/// via A.Outline on the PIC.ShapeProperties element.
/// </summary>
public static void InsertImageWithBorder(
MainDocumentPart mainPart, Body body, string imagePath)
{
ImagePart imagePart = mainPart.AddImagePart(GetImagePartType(imagePath));
using (FileStream stream = new FileStream(imagePath, FileMode.Open))
{
imagePart.FeedData(stream);
}
string relId = mainPart.GetIdOfPart(imagePart);
long cx = (long)(3.0 * EmuPerInch);
long cy = (long)(2.0 * EmuPerInch);
// Build PIC.ShapeProperties with an Outline element for the border.
// Outline width is in EMU. 1pt = 12700 EMU.
var shapeProperties = new PIC.ShapeProperties(
new A.Transform2D(
new A.Offset { X = 0L, Y = 0L },
new A.Extents { Cx = cx, Cy = cy }),
new A.PresetGeometry(
new A.AdjustValueList())
{ Preset = A.ShapeTypeValues.Rectangle },
// The Outline element defines the border
new A.Outline(
// SolidFill sets the border color
new A.SolidFill(
new A.RgbColorModelHex { Val = "2F5496" }), // Dark blue
// PresetDash sets the line style (solid, dash, dot, etc.)
new A.PresetDash { Val = A.PresetLineDashValues.Solid }
)
{
Width = 25400, // 2pt border (12700 EMU per pt)
CompoundLineType = A.CompoundLineValues.Single
}
);
var picture = new PIC.Picture(
new PIC.NonVisualPictureProperties(
new PIC.NonVisualDrawingProperties { Id = 0U, Name = "BorderedImage.png" },
new PIC.NonVisualPictureDrawingProperties()),
new PIC.BlipFill(
new A.Blip { Embed = relId },
new A.Stretch(new A.FillRectangle())),
shapeProperties);
var drawing = new Drawing(
new DW.Inline(
new DW.Extent { Cx = cx, Cy = cy },
new DW.EffectExtent
{
// Must account for border width in effect extent so it is not clipped
LeftEdge = 25400L,
TopEdge = 25400L,
RightEdge = 25400L,
BottomEdge = 25400L
},
new DW.DocProperties { Id = 4U, Name = "BorderedImage" },
new DW.NonVisualGraphicFrameDrawingProperties(
new A.GraphicFrameLocks { NoChangeAspect = true }),
new A.Graphic(
new A.GraphicData(picture)
{ Uri = PicGraphicDataUri })
)
{
DistanceFromTop = 0U,
DistanceFromBottom = 0U,
DistanceFromLeft = 0U,
DistanceFromRight = 0U
});
body.AppendChild(new Paragraph(new Run(drawing)));
}
// ── 5. Image with Alt Text ─────────────────────────────────────────
/// <summary>
/// Inserts an image with alt text for accessibility. The alt text is set
/// on the DocProperties.Description attribute. Screen readers use this.
/// Word also shows it in the "Alt Text" pane.
/// </summary>
public static void InsertImageWithAltText(
MainDocumentPart mainPart, Body body, string imagePath)
{
ImagePart imagePart = mainPart.AddImagePart(GetImagePartType(imagePath));
using (FileStream stream = new FileStream(imagePath, FileMode.Open))
{
imagePart.FeedData(stream);
}
string relId = mainPart.GetIdOfPart(imagePart);
long cx = (long)(3.0 * EmuPerInch);
long cy = (long)(2.0 * EmuPerInch);
// DocProperties.Description is the standard alt text field.
// DocProperties.Title is an optional short title shown in some UIs.
Drawing drawing = BuildDrawingElement(
relId, cx, cy,
docPropId: 5U,
name: "AccessibleImage",
description: "A chart showing quarterly revenue growth from Q1 to Q4 2025");
body.AppendChild(new Paragraph(new Run(drawing)));
}
// ── 6. Image in Header ─────────────────────────────────────────────
/// <summary>
/// Inserts an image into a header part. The image relationship MUST be
/// added to the HeaderPart, NOT the MainDocumentPart. If you add it
/// to MainDocumentPart, Word will show a broken image in the header
/// because relationship IDs are scoped to their containing part.
/// </summary>
public static void InsertImageInHeader(HeaderPart headerPart, string imagePath)
{
// CRITICAL: AddImagePart to headerPart, not mainDocumentPart!
// Each OpenXML part has its own relationship namespace.
// An rId in the header must point to a relationship in the header's .rels file.
ImagePart imagePart = headerPart.AddImagePart(GetImagePartType(imagePath));
using (FileStream stream = new FileStream(imagePath, FileMode.Open))
{
imagePart.FeedData(stream);
}
// GetIdOfPart must also be called on headerPart
string relId = headerPart.GetIdOfPart(imagePart);
long cx = (long)(1.5 * EmuPerInch); // Company logo, typically small
long cy = (long)(0.5 * EmuPerInch);
Drawing drawing = BuildDrawingElement(
relId, cx, cy,
docPropId: 6U,
name: "HeaderLogo",
description: "Company logo");
// Headers use the Header element with Paragraph children (same as Body)
Header header = headerPart.Header;
Paragraph para = new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }),
new Run(drawing));
header.AppendChild(para);
}
// ── 7. Image in Table Cell ─────────────────────────────────────────
/// <summary>
/// Inserts an image into a table cell, sized to fit. Table cells constrain
/// content width, so we calculate appropriate dimensions to avoid overflow.
/// The image part is still added to MainDocumentPart (the cell is in the body).
/// </summary>
/// <param name="mainPart">MainDocumentPart (owns the relationship).</param>
/// <param name="cell">The TableCell to insert the image into.</param>
/// <param name="imagePath">Path to the image file.</param>
public static void InsertImageInTableCell(
MainDocumentPart mainPart, TableCell cell, string imagePath)
{
ImagePart imagePart = mainPart.AddImagePart(GetImagePartType(imagePath));
using (FileStream stream = new FileStream(imagePath, FileMode.Open))
{
imagePart.FeedData(stream);
}
string relId = mainPart.GetIdOfPart(imagePart);
// Determine cell width from TableCellWidth if available.
// TableCellWidth.Width is in DXA (twentieths of a point).
// If not set, use a reasonable default (e.g., 2 inches).
long maxWidthEmu = (long)(2.0 * EmuPerInch); // default
TableCellProperties? tcPr = cell.GetFirstChild<TableCellProperties>();
TableCellWidth? tcWidth = tcPr?.GetFirstChild<TableCellWidth>();
if (tcWidth?.Width is not null && tcWidth.Type?.Value == TableWidthUnitValues.Dxa)
{
// Convert DXA to EMU: 1 DXA = 1/20 pt = 1/1440 inch = 914400/1440 EMU
int dxa = int.Parse(tcWidth.Width);
maxWidthEmu = (long)(dxa * (EmuPerInch / 1440.0));
}
// Calculate image dimensions to fit within the cell width
(long cx, long cy) = CalculateImageDimensions(imagePath, maxWidthEmu / (double)EmuPerInch);
Drawing drawing = BuildDrawingElement(
relId, cx, cy,
docPropId: 7U,
name: "CellImage",
description: null);
// A TableCell MUST contain at least one Paragraph.
// We add the image inside that paragraph.
Paragraph para = cell.GetFirstChild<Paragraph>() ?? cell.AppendChild(new Paragraph());
para.AppendChild(new Run(drawing));
}
// ── 8. Replace Existing Image ──────────────────────────────────────
/// <summary>
/// Replaces an existing image by updating the ImagePart data behind a
/// known relationship ID. The Blip.Embed attribute (rId) stays the same;
/// only the binary content changes. This avoids needing to rebuild the
/// entire Drawing XML tree.
/// </summary>
/// <param name="mainPart">The MainDocumentPart containing the image relationship.</param>
/// <param name="oldRelId">The existing relationship ID (e.g., "rId5") of the image to replace.</param>
/// <param name="newImagePath">Path to the replacement image file.</param>
public static void ReplaceExistingImage(
MainDocumentPart mainPart, string oldRelId, string newImagePath)
{
// Look up the existing ImagePart by its relationship ID
OpenXmlPart part = mainPart.GetPartById(oldRelId);
if (part is not ImagePart imagePart)
{
throw new InvalidOperationException(
$"Relationship {oldRelId} does not point to an ImagePart.");
}
// Feed new image data into the existing part.
// This replaces the binary content while keeping the same rId.
using (FileStream stream = new FileStream(newImagePath, FileMode.Open))
{
imagePart.FeedData(stream);
}
// NOTE: If the new image has different dimensions, you should also
// update the Extent.Cx/Cy and A.Extents.Cx/Cy in the Drawing element.
// Find all Blip elements referencing this relId:
//
// var blips = mainPart.Document.Descendants<A.Blip>()
// .Where(b => b.Embed == oldRelId);
// foreach (var blip in blips)
// {
// // Navigate up to find the Extent and A.Extents to update dimensions
// }
}
// ── 9. SVG with PNG Fallback ───────────────────────────────────────
/// <summary>
/// Inserts an SVG image with a PNG fallback for compatibility.
/// Word 2019+ supports SVG natively; older versions show the PNG.
/// The SVG is referenced via an extension element (SvgBlip) inside the Blip,
/// while the Blip.Embed itself points to the PNG fallback.
/// </summary>
public static void InsertSvgWithPngFallback(
MainDocumentPart mainPart, Body body,
string svgPath, string pngFallbackPath)
{
// Add PNG fallback as the primary image part
ImagePart pngPart = mainPart.AddImagePart(ImagePartType.Png);
using (FileStream pngStream = new FileStream(pngFallbackPath, FileMode.Open))
{
pngPart.FeedData(pngStream);
}
string pngRelId = mainPart.GetIdOfPart(pngPart);
// Add SVG as a separate image part
ImagePart svgPart = mainPart.AddImagePart(ImagePartType.Svg);
using (FileStream svgStream = new FileStream(svgPath, FileMode.Open))
{
svgPart.FeedData(svgStream);
}
string svgRelId = mainPart.GetIdOfPart(svgPart);
long cx = (long)(3.0 * EmuPerInch);
long cy = (long)(3.0 * EmuPerInch);
// The Blip.Embed points to the PNG fallback.
// The SVG is added as an extension element (asvg:svgBlip) inside the Blip.
// Namespace: http://schemas.microsoft.com/office/drawing/2016/SVG/main
var blip = new A.Blip { Embed = pngRelId };
// Add SVG extension to the Blip using BlipExtensionList
var svgExtension = new A.BlipExtensionList(
new A.BlipExtension(
// The SVG blip element references the SVG image part
new OpenXmlUnknownElement(
"asvg", "svgBlip",
"http://schemas.microsoft.com/office/drawing/2016/SVG/main")
// NOTE: In production, set the r:embed attribute on this element
// to svgRelId. OpenXmlUnknownElement requires manual attribute setting.
)
{ Uri = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}" }
);
blip.Append(svgExtension);
var picture = new PIC.Picture(
new PIC.NonVisualPictureProperties(
new PIC.NonVisualDrawingProperties { Id = 0U, Name = "SvgImage.svg" },
new PIC.NonVisualPictureDrawingProperties()),
new PIC.BlipFill(
blip,
new A.Stretch(new A.FillRectangle())),
new PIC.ShapeProperties(
new A.Transform2D(
new A.Offset { X = 0L, Y = 0L },
new A.Extents { Cx = cx, Cy = cy }),
new A.PresetGeometry(new A.AdjustValueList())
{ Preset = A.ShapeTypeValues.Rectangle }));
var drawing = new Drawing(
new DW.Inline(
new DW.Extent { Cx = cx, Cy = cy },
new DW.EffectExtent
{
LeftEdge = 0L, TopEdge = 0L,
RightEdge = 0L, BottomEdge = 0L
},
new DW.DocProperties { Id = 9U, Name = "SvgImage" },
new DW.NonVisualGraphicFrameDrawingProperties(
new A.GraphicFrameLocks { NoChangeAspect = true }),
new A.Graphic(
new A.GraphicData(picture)
{ Uri = PicGraphicDataUri })
)
{
DistanceFromTop = 0U,
DistanceFromBottom = 0U,
DistanceFromLeft = 0U,
DistanceFromRight = 0U
});
body.AppendChild(new Paragraph(new Run(drawing)));
}
// ── 10. Calculate Image Dimensions ─────────────────────────────────
/// <summary>
/// Reads the actual pixel dimensions of an image file (PNG or JPEG) and
/// calculates EMU values that fit within a maximum width while maintaining
/// the original aspect ratio. Uses raw byte reading to avoid a dependency
/// on System.Drawing (which is Windows-only on modern .NET).
/// </summary>
/// <param name="imagePath">Path to a PNG or JPEG image file.</param>
/// <param name="maxWidthInches">Maximum allowed width in inches.</param>
/// <returns>Tuple of (cx, cy) in EMU, scaled to fit maxWidthInches.</returns>
/// <remarks>
/// For production use, consider SkiaSharp or SixLabors.ImageSharp for
/// cross-platform image metadata reading with broader format support.
/// This implementation handles PNG and JPEG only.
/// </remarks>
public static (long cx, long cy) CalculateImageDimensions(
string imagePath, double maxWidthInches)
{
// Read pixel dimensions from the image file header.
// We parse PNG IHDR or JPEG SOF0 markers directly to avoid
// pulling in System.Drawing.Common (Windows-only on .NET 6+).
(int widthPx, int heightPx, double dpiX, double dpiY) = ReadImageMetadata(imagePath);
// Calculate actual size in inches based on pixel count and DPI
double widthInches = widthPx / dpiX;
double heightInches = heightPx / dpiY;
// Scale down if wider than maxWidthInches, preserving aspect ratio
if (widthInches > maxWidthInches)
{
double scale = maxWidthInches / widthInches;
widthInches = maxWidthInches;
heightInches *= scale;
}
long cx = (long)(widthInches * EmuPerInch);
long cy = (long)(heightInches * EmuPerInch);
return (cx, cy);
}
/// <summary>
/// Reads width, height, and DPI from a PNG or JPEG file header.
/// Returns 96 DPI as default if DPI metadata is not found.
/// </summary>
private static (int widthPx, int heightPx, double dpiX, double dpiY) ReadImageMetadata(
string imagePath)
{
const double DefaultDpi = 96.0;
byte[] header = new byte[32];
using var fs = new FileStream(imagePath, FileMode.Open, FileAccess.Read);
int bytesRead = fs.Read(header, 0, header.Length);
// PNG: starts with 0x89 0x50 0x4E 0x47 (‰PNG)
// IHDR chunk is always first; width and height are at bytes 16-23 (big-endian)
if (bytesRead >= 24 &&
header[0] == 0x89 && header[1] == 0x50 &&
header[2] == 0x4E && header[3] == 0x47)
{
int width = (header[16] << 24) | (header[17] << 16) |
(header[18] << 8) | header[19];
int height = (header[20] << 24) | (header[21] << 16) |
(header[22] << 8) | header[23];
// PNG DPI is in the pHYs chunk (not in IHDR); use default for simplicity
return (width, height, DefaultDpi, DefaultDpi);
}
// JPEG: starts with 0xFF 0xD8
// Scan for SOF0 (0xFF 0xC0) marker to find dimensions
if (bytesRead >= 2 && header[0] == 0xFF && header[1] == 0xD8)
{
fs.Position = 2;
while (fs.Position < fs.Length - 1)
{
int b = fs.ReadByte();
if (b != 0xFF) continue;
int marker = fs.ReadByte();
if (marker == -1) break;
// SOF0 (0xC0) or SOF2 (0xC2, progressive)
if (marker == 0xC0 || marker == 0xC2)
{
byte[] sof = new byte[7];
if (fs.Read(sof, 0, 7) == 7)
{
// SOF structure: length(2) + precision(1) + height(2) + width(2)
int height = (sof[3] << 8) | sof[4];
int width = (sof[5] << 8) | sof[6];
return (width, height, DefaultDpi, DefaultDpi);
}
break;
}
// Skip other markers: read 2-byte length and advance
if (marker is not (0xD0 or 0xD1 or 0xD2 or 0xD3 or 0xD4 or
0xD5 or 0xD6 or 0xD7 or 0xD8 or 0xD9 or 0x01))
{
byte[] lenBytes = new byte[2];
if (fs.Read(lenBytes, 0, 2) < 2) break;
int len = (lenBytes[0] << 8) | lenBytes[1];
if (len < 2) break;
fs.Position += len - 2;
}
}
}
// Fallback: cannot determine dimensions; return a reasonable default
// Caller should handle this gracefully.
return (300, 200, DefaultDpi, DefaultDpi);
}
// ── 11. Reusable Drawing Builder (Inline) ──────────────────────────
/// <summary>
/// Builds a complete Drawing element for an inline image. This is the
/// reusable core that most insertion methods delegate to.
/// </summary>
/// <param name="relId">Relationship ID pointing to the ImagePart (e.g., "rId4").</param>
/// <param name="cx">Image width in EMU. Must be positive.</param>
/// <param name="cy">Image height in EMU. Must be positive.</param>
/// <param name="docPropId">Unique ID for DocProperties within the document.
/// Each Drawing in a document must have a distinct DocProperties.Id.</param>
/// <param name="name">Name for DocProperties (shows in Word selection pane).</param>
/// <param name="description">Alt text for accessibility. Null if not needed.</param>
/// <returns>A fully constructed Drawing element ready to append to a Run.</returns>
public static Drawing BuildDrawingElement(
string relId, long cx, long cy,
uint docPropId, string name, string? description)
{
// ── Complete element hierarchy ──
// Drawing
// └─ DW.Inline
// ├─ DW.Extent (cx, cy) ← bounding box size
// ├─ DW.EffectExtent ← extra space for effects
// ├─ DW.DocProperties (id, name, descr) ← identity + alt text
// ├─ DW.NonVisualGraphicFrameDrawingProperties
// │ └─ A.GraphicFrameLocks ← lock aspect ratio
// └─ A.Graphic
// └─ A.GraphicData (uri = picture namespace)
// └─ PIC.Picture
// ├─ PIC.NonVisualPictureProperties
// │ ├─ PIC.NonVisualDrawingProperties
// │ └─ PIC.NonVisualPictureDrawingProperties
// ├─ PIC.BlipFill
// │ ├─ A.Blip (embed = relId)
// │ └─ A.Stretch → A.FillRectangle
// └─ PIC.ShapeProperties
// ├─ A.Transform2D
// │ ├─ A.Offset (0, 0)
// │ └─ A.Extents (cx, cy) ← MUST match DW.Extent!
// └─ A.PresetGeometry (rect)
var docProps = new DW.DocProperties
{
Id = docPropId,
Name = name
};
if (description is not null)
{
docProps.Description = description;
}
var picture = new PIC.Picture(
new PIC.NonVisualPictureProperties(
new PIC.NonVisualDrawingProperties
{
Id = 0U,
Name = name
},
new PIC.NonVisualPictureDrawingProperties()),
new PIC.BlipFill(
new A.Blip
{
Embed = relId,
// CompressionState controls image quality vs file size.
// Print = high quality, Screen = medium, Email = low, None = original
CompressionState = A.BlipCompressionValues.Print
},
new A.Stretch(new A.FillRectangle())),
new PIC.ShapeProperties(
new A.Transform2D(
new A.Offset { X = 0L, Y = 0L },
new A.Extents { Cx = cx, Cy = cy }), // MUST match DW.Extent
new A.PresetGeometry(
new A.AdjustValueList())
{ Preset = A.ShapeTypeValues.Rectangle }));
var inline = new DW.Inline(
new DW.Extent { Cx = cx, Cy = cy }, // MUST match A.Extents
new DW.EffectExtent
{
LeftEdge = 0L,
TopEdge = 0L,
RightEdge = 0L,
BottomEdge = 0L
},
docProps,
new DW.NonVisualGraphicFrameDrawingProperties(
new A.GraphicFrameLocks { NoChangeAspect = true }),
new A.Graphic(
new A.GraphicData(picture)
{ Uri = PicGraphicDataUri }))
{
DistanceFromTop = 0U,
DistanceFromBottom = 0U,
DistanceFromLeft = 0U,
DistanceFromRight = 0U
};
return new Drawing(inline);
}
// ── Private Helpers ────────────────────────────────────────────────
/// <summary>
/// Builds a DW.Anchor element for floating images with configurable wrapping.
/// </summary>
private static DW.Anchor BuildAnchorElement(
string relId, long cx, long cy,
uint docPropId, string name,
OpenXmlElement wrapElement,
bool behindDoc)
{
return new DW.Anchor(
new DW.SimplePosition { X = 0L, Y = 0L },
new DW.HorizontalPosition(
new DW.PositionOffset("0"))
{ RelativeFrom = DW.HorizontalRelativePositionValues.Column },
new DW.VerticalPosition(
new DW.PositionOffset("0"))
{ RelativeFrom = DW.VerticalRelativePositionValues.Paragraph },
new DW.Extent { Cx = cx, Cy = cy },
new DW.EffectExtent
{
LeftEdge = 0L,
TopEdge = 0L,
RightEdge = 0L,
BottomEdge = 0L
},
wrapElement,
new DW.DocProperties { Id = docPropId, Name = name },
new DW.NonVisualGraphicFrameDrawingProperties(
new A.GraphicFrameLocks { NoChangeAspect = true }),
new A.Graphic(
new A.GraphicData(
new PIC.Picture(
new PIC.NonVisualPictureProperties(
new PIC.NonVisualDrawingProperties
{
Id = 0U,
Name = name
},
new PIC.NonVisualPictureDrawingProperties()),
new PIC.BlipFill(
new A.Blip { Embed = relId },
new A.Stretch(new A.FillRectangle())),
new PIC.ShapeProperties(
new A.Transform2D(
new A.Offset { X = 0L, Y = 0L },
new A.Extents { Cx = cx, Cy = cy }),
new A.PresetGeometry(
new A.AdjustValueList())
{ Preset = A.ShapeTypeValues.Rectangle }))
)
{ Uri = PicGraphicDataUri })
)
{
DistanceFromTop = 0U,
DistanceFromBottom = 0U,
DistanceFromLeft = 114300U,
DistanceFromRight = 114300U,
SimplePos = false,
RelativeHeight = 251658240U,
BehindDoc = behindDoc,
Locked = false,
LayoutInCell = true,
AllowOverlap = true
};
}
/// <summary>
/// Maps file extensions to OpenXML PartTypeInfo values via ImagePartType.
/// In SDK 3.x, ImagePartType is a static class whose members return PartTypeInfo.
/// </summary>
private static PartTypeInfo GetImagePartType(string imagePath)
{
string ext = Path.GetExtension(imagePath).ToLowerInvariant();
return ext switch
{
".png" => ImagePartType.Png,
".jpg" or ".jpeg" => ImagePartType.Jpeg,
".gif" => ImagePartType.Gif,
".bmp" => ImagePartType.Bmp,
".tif" or ".tiff" => ImagePartType.Tiff,
".svg" => ImagePartType.Svg,
".emf" => ImagePartType.Emf,
".wmf" => ImagePartType.Wmf,
".ico" => ImagePartType.Icon,
_ => throw new NotSupportedException(
$"Image format '{ext}' is not supported by OpenXML.")
};
}
}
Loaded 100 of 398 files, more files were not shown because too many files have changed in this diff. Show more