diff --git a/skills/dafna-decision-template/SKILL.md b/skills/dafna-decision-template/SKILL.md new file mode 100644 index 0000000..02faa21 --- /dev/null +++ b/skills/dafna-decision-template/SKILL.md @@ -0,0 +1,292 @@ +--- +name: dafna-decision-template +description: > + ייצוא מסמכי DOCX עבור ועדת ערר לתכנון ובניה מחוז ירושלים (יו"ר עו"ד דפנה תמיר), + באמצעות שימוש בסגנונות המוגדרים בטמפלט Word של דפנה (`טיוטת החלטה.dotx`). + הסקיל מחיל את סגנונות הטמפלט (Normal/Heading 1/Heading 2/Quote/List Paragraph) + על תוכן שנכתב מתוכנתית — בלי להגדיר פונט/גודל/RTL/שוליים ידנית. + + טריגרים: "ייצוא החלטה", "ייצוא ניתוח משפטי", "DOCX של דפנה", + "טמפלט החלטה", "סגנונות החלטה", "Word עם David", "מסמך ועדת ערר", + "הורדת החלטה", "פסקה ממוספרת בעברית", "(א) (ב) (ג)", + כל בקשה להוציא מסמך Word המבוסס על טמפלט דפנה. +--- + +# Dafna Decision Template — ייצוא DOCX מסגנונות טמפלט + +## מה זה עושה + +סקיל זה הוא **layer דק מעל `python-docx`** שמטעין את הטמפלט של דפנה +([skills/docx/decision_template.docx](../docx/decision_template.docx) — +מומר מ-`data/training/טיוטת החלטה.dotx`) וכותב תוכן חדש על בסיסו, **על ידי +שיוך שמות סגנונות בלבד** (`paragraph.style = "Heading 2"`). העיצוב — +פונט David, RTL, גדלים, הזחות, מספור אוטומטי — מגיע מה-`styles.xml` +של הטמפלט. + +השירות המעשי נמצא ב- +[`mcp-server/src/legal_mcp/services/analysis_docx_exporter.py`](../../mcp-server/src/legal_mcp/services/analysis_docx_exporter.py). +הסקיל מתעד את **הכללים** שה-service מיישם, כך שניתן לשחזר/להרחיב אותם +בסקריפטים אחרים ללא צורך לגלות הכל מחדש. + +--- + +## 🔴 קריטי — 5 עקרונות שחייבים לזכור + +### 1. **לא להגדיר font/size/indent ידנית** — תמיד style +```python +# ❌ אל +run.font.name = "David"; run.font.size = Pt(13) +paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.RIGHT + +# ✅ כן +paragraph = doc.add_paragraph(style="Normal") # כל השאר מגיע מהטמפלט +``` + +### 2. **RTL חייב דגלים ב-paragraph וב-run — גם אם הסגנון כולל bidi** +```python +# ❌ אל — עברית תצא ב-Times New Roman כי Word לא יודע שזה complex-script +p = doc.add_paragraph(style="Normal") +p.add_run("טקסט בעברית") + +# ✅ כן — מסמנים את ה-run כ-RTL +p = doc.add_paragraph(style="Normal") +_mark_paragraph_rtl(p) +run = p.add_run("טקסט בעברית") +_mark_run_rtl(run) # ← מוסיף ל-rPr +``` +בלי הדגל הזה, Word נופל בחזרה ל-`ascii` font (Times New Roman) במקום +ל-`cs` font (David). זו הסיבה הנפוצה ביותר לתוצאה "עברית ב-Times New Roman". + +ראה [references/rtl-runs.md](references/rtl-runs.md) לפרטים מלאים. + +### 3. **`.dotx` לא נטען ישירות** — יש להמיר ל-`.docx` פעם אחת +python-docx פותח רק `.docx`. להמרה: `scripts/convert_decision_template.py` +(בשורש הפרויקט). יש להסיר: +- `word/glossary/*` parts +- Override entries שמצביעים אליהם ב-`[Content_Types].xml` +- Relationship `glossaryDocument` ב-`word/_rels/document.xml.rels` +- להחליף content-type מ-`...template.main+xml` ל-`...document.main+xml` + +ראה [references/dotx-to-docx.md](references/dotx-to-docx.md). + +### 4. **Title לא טוב ככותרת עברית** — השתמש ב-Heading 1 +הסגנון `Title` בטמפלט מפנה ל-theme fonts (`majorFont`). ב-theme1.xml: +`majorFont.latin = "Aptos Display"`, `majorFont.cs = ""` (ריק). +לכן עברית תרונדר ב-Latin fallback. + +`Heading 1` יורש cs="David" מ-`Normal` — השתמש בו לכותרת ראשית. + +### 5. **מספור אוטומטי רק ב-`List Paragraph`** — decimal בלבד +`List Paragraph` (styleId `a0`) מקושר ל-`numId=1 → numFmt=decimal`. +כלומר Word יוסיף אוטומטית "1.", "2.", "3." לכל פסקה עם הסגנון הזה. + +- שורות שמתחילות ב-`N.` → הסר את המספר מהטקסט, החל `List Paragraph`. +- שורות שמתחילות ב-`(א)` `(ב)` → השתמש ב-`List Paragraph` **עם הסרת ``**, + כי המספור בעברית נכתב בעצמו על ידי המחבר. +- Bullets (`- `, `• `) → הסר את הסימן, השאר `Normal`. + +ראה [references/style-mapping.md](references/style-mapping.md). + +--- + +## הטמפלט — מיפוי סגנונות + +מיפוי מלא של הסגנונות בטמפלט ל-content type. זה ה"חוזה" של הסקיל — +כל שינוי דרך השירות צריך להיצמד אליו. + +| תוכן | style name | הערה | +|------|-----------|------| +| כותרת מסמך ("ניתוח משפטי וכתיבת עמדה בערר X") | `Heading 1` | יורש cs="David" | +| כותרת מקטע ראשי (רקע דיוני, פסיקה כללית, סוגיות להכרעה, מסקנות) | `Heading 2` | bold + underline | +| כותרת משנה בתוך סוגיה (טענה (claim), תשובה, ניתוח, נקודות פתוחות, …) | `Heading 2` | | +| שם subsection (טענת סף 1, סוגיה 2) | `Normal` + bold run | | +| פסקת רקע רגילה | `Normal` | David 13pt, justify, RTL | +| פסקת רשימה ממוספרת (1., 2., 3.) | `List Paragraph` | Word ימספר | +| פסקת רשימה בעברית ((א), (ב)) | `List Paragraph` + `_strip_numpr()` | המספור נשאר בטקסט | +| ציטוט פסיקה | `Quote` | bold + הזחה | +| citation / מקור | `Normal` + italic | | + +ראה [references/style-mapping.md](references/style-mapping.md) לטבלה +מורחבת עם ה-XML של כל סגנון. + +--- + +## תוכן המקור — `analysis-and-research.md` + +הקובץ נכתב על ידי `legal-analyst` agent. מבנה מצופה: + +```markdown +# ניתוח משפטי וכתיבת עמדה — ערר {case_number} +תאריך: DD.MM.YYYY + +## רקע דיוני +{prose content} + +## עובדות מוסכמות +1. ... + +## עובדות שנויות במחלוקת +1. ... + +## טענות סף +### טענה {n}: {title} +**עמדת המבקשת:** ... +**עמדת ועדת הערר:** [ימולא ע"י יו"ר הוועדה] + +## סוגיות להכרעה +### סוגיה {n}: {title} +**ממצאים עובדתיים:** +... +**טענה (claim):** +העורר טוען כי: +(א) ... +(ב) ... +**ניתוח:** +... +- **נקודות פתוחות:** + 1. ... +- **הערכה ראשונית:** {prose} +**עמדת ועדת הערר:** [ימולא ע"י יו"ר הוועדה] + +## מסקנות +... +``` + +ה-parser (`research_md.py`) חולק את זה ל-`threshold_claims[]`, `issues[]`, +prose sections, ו-`conclusions`. חשוב: **שמות sections חייבים להכיל את +המילים המופתח** (`רקע דיוני`, `טענות סף`, …) — ה-parser עובד לפי matching של +keywords, לא לפי מספר ה-H2. + +--- + +## זרימת הייצוא + +סדר המקטעים ב-DOCX (גם אם סדר ה-H2 ב-MD שונה): + +1. **כותרת** (Heading 1) + שורת תאריך +2. **רקע** (Heading 2) — `represented_party`, `procedural_background`, + `agreed_facts`, `disputed_facts` (אם קיימים) +3. **פסיקה כללית** (Heading 2) — `case_precedents` עם `section_id=NULL` +4. **טענות סף** (Heading 2) — לכל subsection: title, fields, chair_position, precedents +5. **סוגיות להכרעה** (Heading 2) — אותה חלוקה +6. **מסקנות** (Heading 2) — בסוף + +הסיבה: בקריאה משפטית נכון להציג תחילה רקע ועובדות, ואז את הדיון. פסיקה +כללית מופיעה לפני הסוגיות כי היא רוחבית. + +--- + +## עיבוד שורות — `_classify_line()` + +ה-service מפרש כל שורה של content לאחת מ-6 קטגוריות: + +| kind | דוגמה | מה קורה | +|------|--------|---------| +| `label_heading` | `**נקודות פתוחות:**` (שורה שלמה, כולל `- **X:**`) | Heading 2 | +| `label_heading` (plain) | `העורר טוען כי:` (שורה קצרה שמסתיימת ב-`:`) | Heading 2 | +| `inline_label` | `**שאלה עקרונית:** מה...` | Normal עם label bold inline | +| `numbered` | `1. הנספח אינו מחייב` | List Paragraph, המספר מוסר | +| `bullet` | `- nevo (קלאסי)...` | Normal, הסימן מוסר | +| `heb_letter` | `(א) הנספח אינו...` | List Paragraph + strip numPr | +| `plain` | רגיל | Normal | + +בנוסף, **inline `**...**`** מעובד בכל ריצה דרך `_add_runs_with_inline_bold` +— כל `**word**` הופך ל-run נפרד עם `bold=True`. + +ראה [references/line-classification.md](references/line-classification.md). + +--- + +## מקפים — מדיניות + +המשתמש (דפנה) ביקשה: **"לא רוצה מקפים בכלל"**. הסקיל מסיר: + +- `—` (em-dash, U+2014) +- `–` (en-dash, U+2013) + +מכל טקסט שהקוד כותב למסמך (גם תוכן מהמקור). מקפים רגילים (`-`) +נשמרים. הפונקציה: `_no_dash()`. + +--- + +## שדות ריקים — placeholder + +שדה `chair_position` (עמדת ועדת הערר) שמכיל אחד מהסימנים הריקים +(`[ימולא ע"י יו"ר הוועדה]`, `[טרם מולא]`, וכד') → מוחלף ב- +`[טרם מולאה עמדת ועדת הערר]` בסגנון italic. זה סימן ויזואלי ברור +שנשאר עדיין להשלים. + +--- + +## שימוש — API + +```python +from legal_mcp.services.analysis_docx_exporter import build_analysis_docx + +path = await build_analysis_docx("8070-25") +# → data/cases/8070-25/exports/ניתוח-משפטי-v{N}.docx +``` + +Endpoint ציבורי: `GET /api/cases/{case_number}/research/analysis/export-docx` + +--- + +## התמודדות עם בעיות נפוצות + +### "עברית יוצאת ב-Times New Roman" +- חסר `` ב-run. הוסף `_mark_run_rtl(run)` אחרי כל `add_run`. +- בדוק: הסגנון שאתה משתמש בו יש בו `cs="David"` (או יורש מ-Normal שיש לו)? + +### "המספור כפול: '1. (א) ...'" +- אתה משתמש ב-`List Paragraph` על שורה עם `(א)`. צריך `_strip_numpr(para)`. + +### "כוכביות `**...**` מופיעות במסמך" +- הקפד להעביר תוכן דרך `_add_runs_with_inline_bold()`, לא `paragraph.add_run()`. + +### "התבנית לא נטענת" +- הרץ מחדש `python scripts/convert_decision_template.py`. בדוק שה-docx + שנוצר פותחיb ב-Word ללא שגיאות. + +### "המספור ברשימה השנייה ממשיך מהראשונה (4,5,6 במקום 1,2,3)" +- ידוע. הפתרון: להוסיף override של `numId` ברמת ה-paragraph הראשון של + הרשימה החדשה. עדיין לא מיושם — ראה "שיפורים עתידיים". + +--- + +## שיפורים עתידיים (TODO) + +- [ ] Reset של מספור List Paragraph בין רשימות נפרדות (לא רציף). +- [ ] תמיכה ב-`פיסקת רשימה - ללא מספור` (styleId `-`) לbullets. +- [ ] עיצוב מותאם לסוג הערר (1xxx/8xxx/9xxx) — כרגע אחיד. +- [ ] אפשרות להוריד רק מקטעים נבחרים (רק טענות סף, רק מסקנות, וכו'). + +--- + +## מבנה קבצים + +``` +skills/dafna-decision-template/ +├── SKILL.md ← הקובץ הזה +└── references/ + ├── dotx-to-docx.md ← איך ממירים .dotx ל-.docx + ├── rtl-runs.md ← למה `` חשוב בכל run + ├── style-mapping.md ← מיפוי מלא של סגנונות הטמפלט + └── line-classification.md ← לוגיקת _classify_line() + +mcp-server/src/legal_mcp/services/ +└── analysis_docx_exporter.py ← המימוש המעשי + +scripts/ +└── convert_decision_template.py ← המרת dotx → docx (חד-פעמי) + +skills/docx/ +└── decision_template.docx ← הטמפלט המומר (artifact) +``` + +--- + +## היסטוריה + +| גרסה | תאריך | שינוי | +|-------|-------|-------| +| v1.0 | 2026-04-16 | יצירת הסקיל. מיפוי ראשוני של סגנונות, תמיכה ב-RTL runs, inline bold, (א)(ב), Heading 1/2, מקטעי רקע. | diff --git a/skills/dafna-decision-template/references/dotx-to-docx.md b/skills/dafna-decision-template/references/dotx-to-docx.md new file mode 100644 index 0000000..998bc06 --- /dev/null +++ b/skills/dafna-decision-template/references/dotx-to-docx.md @@ -0,0 +1,58 @@ +# המרת `.dotx` → `.docx` עבור python-docx + +## למה + +python-docx **לא יודע לפתוח** קובצי Word Template (`.dotx`). ניסיון לפתיחה +זורק: +``` +ValueError: file 'X.dotx' is not a Word file, content type is +'application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml' +``` + +כי `main document part` של `.dotx` מסומן כ-template, לא כ-document. + +## הפתרון — המרה חד-פעמית + +קובץ `.dotx` הוא ZIP שכולל את אותם parts כמו `.docx` + `word/glossary/` +(building blocks). להמרה: + +1. פתח את ה-ZIP. +2. **הסר** את כל ה-parts תחת `word/glossary/`. +3. **תקן** `[Content_Types].xml`: + - החלף `template.main+xml` ב-`document.main+xml` + - הסר `` entries שמצביעים ל-`/word/glossary/...` +4. **תקן** `word/_rels/document.xml.rels`: + - הסר את ה-`` עם + `Type=".../relationships/glossaryDocument"` +5. שמור מחדש כ-ZIP עם סיומת `.docx`. + +## הסקריפט + +[`scripts/convert_decision_template.py`](../../../scripts/convert_decision_template.py) +(בשורש הפרויקט) עושה את זה. הרץ אותו: + +- פעם אחת אחרי clone של הפרויקט (אם `skills/docx/decision_template.docx` + לא קיים). +- בכל פעם שדפנה מעדכנת את `data/training/טיוטת החלטה.dotx`. + +```bash +python scripts/convert_decision_template.py +# → skills/docx/decision_template.docx +``` + +הסקריפט כולל verification שבודק שהקובץ שנוצר נטען נקי ב-python-docx +ושהסגנונות הקריטיים (Normal, Heading 2, Quote, List Paragraph, Title) +נמצאים בו. + +## למה לא `docxtpl`? + +`docxtpl` מיועד ל-**placeholder substitution** סגנון Jinja2 +(`{{ variable }}`). הטמפלט שלנו לא מכיל placeholders — אנחנו מרכיבים +תוכן דינמי. `python-docx` על טמפלט `.docx` נקי מספיק לגמרי. + +## הימנע מ- + +- **אל תנסה** להוריד את ה-glossary רק מ-`[Content_Types].xml` בלי להסיר + את ה-`` שמפנה אליו → תקבל dangling reference. +- **אל תנסה** להשתמש ב-`.dotx` ישירות דרך `zipfile` + לבנות Document + ידנית — חוסך 10 שורות אבל מאבד את כל ה-robustness של python-docx. diff --git a/skills/dafna-decision-template/references/line-classification.md b/skills/dafna-decision-template/references/line-classification.md new file mode 100644 index 0000000..6aa04e9 --- /dev/null +++ b/skills/dafna-decision-template/references/line-classification.md @@ -0,0 +1,115 @@ +# סיווג שורות — `_classify_line()` + +כל שורה של content מה-MD עוברת דרך `_classify_line()` שמחזירה +`(kind, clean_text)`. הקטגוריה מכתיבה איזה סגנון Word יוחל. + +## טבלת הקטגוריות + +| kind | regex | clean_text | נמפה ל-style | +|------|--------|-----------|--------------| +| `label_heading` | `^\s*\*\*([^\n*]+?):\*\*\s*$` | `"$1"` | `Heading 2` | +| `label_heading` (plain) | `^\s*([^\n:]{2,40}):\s*$` | `"$1"` | `Heading 2` | +| `inline_label` | `^\s*\*\*([^\n*]+?):\*\*\s+(.+)$` | `"$1\x00$2"` | `Normal` + bold label + value | +| `numbered` | `^\s*(\d+)[.)]\s+(.+)$` | `"$2"` | `List Paragraph` | +| `bullet` | `^\s*[\-\u2022\*\u25CF\u25E6]\s+(.+)$` | `"$1"` | `Normal` (marker stripped) | +| `heb_letter` | `^\s*\([א-ת]\)\s+` | **full line** (marker kept) | `List Paragraph` + `_strip_numpr()` | +| `plain` | fallback | line | `Normal` | + +## סדר הבדיקות (חשוב!) + +``` +1. STANDALONE_LABEL_RE (**X:**) +2. INLINE_LABEL_RE (**X:** value) +3. NUMBERED_LINE_RE (1. X) +4. BULLET_LINE_RE (- X) + re-check inside: + 4a. STANDALONE inside → label_heading + 4b. INLINE inside → inline_label +5. HEB_LETTER_LINE_RE ((א) X) +6. PLAIN_LABEL_RE (X:) — last because it's broad +7. plain +``` + +## דוגמאות מהשטח + +### input: `- **נקודות פתוחות:**` +- `BULLET_LINE_RE` תופס → `inner = "**נקודות פתוחות:**"` +- `STANDALONE_LABEL_RE` על ה-inner → `label_heading`, text = `"נקודות פתוחות"` +- יוצא כ-Heading 2. + +### input: `- **נקודות פתוחות:** האם המקדם...` +- `BULLET_LINE_RE` תופס → inner = `"**נקודות פתוחות:** האם..."` +- `INLINE_LABEL_RE` על ה-inner → `inline_label` +- יוצא כ-Normal עם label "נקודות פתוחות:" bold + value רגיל. + +### input: `1. **שאלה עקרונית:** האם נספח...` +- `NUMBERED_LINE_RE` תופס → `"**שאלה עקרונית:** האם נספח..."` +- יוצא כ-List Paragraph. ה-`**...**` בתוכו יעובד על ידי + `_add_runs_with_inline_bold()` (bold inline run). + +### input: `(א) נספח הבינוי של תכנית...` +- `HEB_LETTER_LINE_RE` תופס +- יוצא כ-List Paragraph עם `_strip_numpr()` — כי המחבר כבר כתב "(א)". + +### input: `העורר טוען כי:` +- לא תואם regex ספציפי +- `PLAIN_LABEL_RE` תופס (23 תווים, מסתיים ב-`:`) +- יוצא כ-Heading 2. + +### input: `פסקה ארוכה: עם עוד תוכן ופסיק, וסוגיה מורכבת.` +- `PLAIN_LABEL_RE` לא תופס (יותר מ-40 תווים לפני `:`) +- נשאר `plain` → Normal. + +## למה הגבלת `PLAIN_LABEL_RE` ל-`{2,40}` + +בלי הגבלה, כל פסקה עם `:` במקום כלשהו הייתה הופכת ל-Heading 2. דוגמה +שצריך למנוע: +``` +טענה חשובה כאן: היא שהוועדה שגתה בכל אופן. +``` +אין כאן כוונה לכותרת — `:` הוא חלק ממשפט. ההגבלה ל-40 תווים מסננת את +רוב המקרים האלה כי רוב headings אמיתיים הם קצרים. + +40 תווים זה ניחוש — אפשר לכוון אם מגלים false positives/negatives. + +## inline bold — `_add_runs_with_inline_bold()` + +אחרי סיווג, הטקסט עדיין יכול להכיל `**word**` באמצע. הפונקציה מחלקת +את המחרוזת ל-runs מתחלפים: + +``` +"העורר טוען **שהתוצאה** שגויה" +→ [ + Run("העורר טוען ", bold=None), + Run("שהתוצאה", bold=True), + Run(" שגויה", bold=None), + ] +``` + +כל run מסומן RTL בנפרד. יוצא ב-Word עם הדגש המקומי בלבד על המילה +`שהתוצאה`. + +## השלמת התמונה + +``` +md content + ↓ splitlines() +for line in lines: + ↓ _classify_line(line) + → (kind, clean_text) + ↓ _emit_content_line(doc, line) + → paragraph with chosen style + ↓ _add_runs_with_inline_bold(paragraph, clean_text) + → runs with inline **bold** rendered + ↓ _mark_run_rtl / _mark_paragraph_rtl + → Hebrew renders in David (cs slot) +``` + +## הוספת קטגוריה חדשה + +אם יש דפוס שלא מזוהה ורצוי למפות אותו: + +1. הוסף regex constant (למעלה בקובץ, אחרי הקיימים). +2. הוסף branch ב-`_classify_line()` לפי הסדר הנכון (ספציפי לפני כללי). +3. הוסף branch ב-`_emit_content_line()` עם הסגנון המתאים. +4. הוסף test case ב-references/line-classification.md (כאן). +5. הרץ על תיק מייצג (למשל 8070-25) וראה שהתוצאה נכונה. diff --git a/skills/dafna-decision-template/references/rtl-runs.md b/skills/dafna-decision-template/references/rtl-runs.md new file mode 100644 index 0000000..74dbaac --- /dev/null +++ b/skills/dafna-decision-template/references/rtl-runs.md @@ -0,0 +1,81 @@ +# למה `` חובה בכל run + +## הבעיה + +כשאתה יוצר `run` ב-python-docx על סגנון עברי מוגדר היטב (למשל Normal עם +`cs="David"`) — עברית עדיין יוצאת ב-Times New Roman. + +## הסיבה + +Word משתמש ב-3 font slots בתוך ``: +- `w:ascii` — תווים לטיניים +- `w:hAnsi` — אותיות מיוחדות אירופיות +- `w:cs` (complex script) — עברית, ערבית, תאית + +ההחלטה איזה slot להשתמש נעשית **לפי סוג הטקסט ב-run** ולפי **דגל רמת +הריצה ``**. בלי הדגל, Word יכול להתייחס לטקסט העברי כ-LTR +(למשל כשהוא מתערבב עם ספרות/לטינית) ולבחור את `ascii` — Times New Roman. + +## הפתרון + +מסמן כל run עברי כ-complex-script: + +```python +from docx.oxml import OxmlElement +from docx.oxml.ns import qn + +def _mark_run_rtl(run): + rPr = run._r.get_or_add_rPr() + if rPr.find(qn("w:rtl")) is None: + rPr.append(OxmlElement("w:rtl")) +``` + +וגם ברמת ה-paragraph (למקרה ש-paragraph mark עצמו משפיע): + +```python +def _mark_paragraph_rtl(paragraph): + pPr = paragraph._p.get_or_add_pPr() + rPr = pPr.find(qn("w:rPr")) + if rPr is None: + rPr = OxmlElement("w:rPr"); pPr.append(rPr) + if rPr.find(qn("w:rtl")) is None: + rPr.append(OxmlElement("w:rtl")) +``` + +## תופעות לוואי של חוסר RTL ברמת ה-run + +1. **Font fallback ל-Times New Roman** — הסימפטום הנפוץ ביותר. +2. **BiDi reordering של פיסוק** — נקודתיים, פסיקים, סוגריים עוברים למקום + הלא נכון. הסימפטום: `"(א)"` הופך ל-`")א("`. +3. **מספרים "נוגדים" ברצף עברי** — `"בשנת 2024 פסקנו"` יכול להיראות + עם המספר במיקום הלא נכון. + +## איך לבדוק שה-RTL חל + +```python +from docx.oxml.ns import qn + +for p in doc.paragraphs: + for r in p.runs: + rPr = r._r.find(qn("w:rPr")) + has_rtl = rPr is not None and rPr.find(qn("w:rtl")) is not None + if not has_rtl and any('\u0590' <= c <= '\u05FF' for c in r.text): + print(f"Missing RTL: {r.text[:40]!r}") +``` + +## זה לא מספיק רק ברמת הסגנון + +זו תפיסה מוטעית נפוצה: "אם הסגנון כולל `` ב-`rPr`, ירש כל ריצה". +**לא נכון**. סגנון נותן ברירת מחדל ל-runs שעדיין לא נוצרו ב-Word GUI — +אבל runs שנוצרו דרך python-docx מקבלים `rPr` ריק, שלא תורש אוטומטית +את ה-rtl מהסגנון. לכן חייבים להוסיף ידנית. + +## הטמפלט של דפנה כדוגמה + +בוחנים את `word/document.xml` של הטמפלט המקורי — כל ריצה עברית כוללת: + +```xml +רקע +``` + +`` נמצא שם **במפורש**. אנחנו מחקים את זה. diff --git a/skills/dafna-decision-template/references/style-mapping.md b/skills/dafna-decision-template/references/style-mapping.md new file mode 100644 index 0000000..3f8216b --- /dev/null +++ b/skills/dafna-decision-template/references/style-mapping.md @@ -0,0 +1,155 @@ +# מיפוי סגנונות — `טיוטת החלטה.dotx` + +מבוסס על ניתוח `word/styles.xml` של הטמפלט. כל הגדרות מצוטטות מה-XML +המקורי. + +## סגנונות בסיסיים (Word-idioms) + +### `Normal` (styleId: `a1`) +ברירת המחדל לפסקאות רגילות. **כל שאר הסגנונות יורשים ממנו** +(`basedOn="a1"`). + +```xml + + + + + + + + + + + +``` + +**מה זה אומר לך:** +- עברית תצא ב-David 13pt (דרך `cs`) +- לטינית תצא ב-Times New Roman 13pt (דרך `ascii`) +- יישור justify (דו-צדדי), RTL +- מרווח 1.5 שורות (`line=360` = 1.5 × 240) + +### `Heading 1` (styleId: `10`, name: `heading 1`) +לכותרת ראשית (כמו "ניתוח משפטי וכתיבת עמדה בערר X"). +```xml + + +``` +Heading 1 **אינו** מציין cs fonts מפורשות, אבל יורש `cs="David"` מ-Normal. +זה מבדיל אותו מ-`Title`, שמפנה ל-theme ריק. + +### `Heading 2` (styleId: `2`) +לכותרות מקטעים ותת-מקטעים. +```xml + + + +``` + +### `Title` (styleId: `a5`) — ⚠️ הימנע +```xml + +``` +מפנה ל-theme. ב-`theme1.xml`: `majorFont.cs = ""` (ריק). → עברית נופלת +ל-`majorFont.latin = "Aptos Display"`. **השתמש ב-Heading 1 במקום.** + +### `Subtitle` (styleId: `a7`) — ⚠️ אותה בעיה של Title +לא בשימוש. + +## סגנונות תוכן + +### `Quote` (styleId: `a9`) +```xml + + + + +``` +לציטוטי פסיקה. הזחה פנימה, bold. + +### `List Paragraph` (styleId: `a0`) +```xml + + + + + + + +``` +**numId=1 מפנה ל-abstractNumId=16**, שמוגדר כ-`decimal` עם `lvlText="%1."`. +כלומר Word יוסיף "1.", "2.", "3." אוטומטית לפני הטקסט. + +**חשוב:** `List Paragraph` הוא היחיד בטמפלט עם numbering אוטומטי. אין +bullet style מוכן. לרשימות עם (א)(ב), **הסר את ה-numPr** ברמת הפסקה: + +```python +from docx.oxml.ns import qn + +def _strip_numpr(paragraph): + pPr = paragraph._p.get_or_add_pPr() + for numPr in pPr.findall(qn("w:numPr")): + pPr.remove(numPr) +``` + +## סגנונות נוספים בטמפלט (לא בשימוש כרגע) + +| styleId | שם | מה | +|---------|----|----| +| `P00`, `P11`, `P22` | תבניות מותאמות אישית | ישן, נראה שלא בשימוש פעיל | +| `12`, `21`, `31` | פיסקת רשימה 1/2/3 | ללא numPr — שימוש ויזואלי בלבד | +| `-` | פיסקת רשימה - ללא מספור | מיועד ל-bullets ללא מספור | +| `14` | ציטוט1 | וריאציה ישנה של Quote | +| `af2` / `af4` | header / footer | לכותרות עליונות/תחתונות | + +## theme (`word/theme/theme1.xml`) + +```xml + + + + + + + + +``` + +**ה-cs ריק ב-theme** — זו הסיבה ש-`Title` (שמפנה ל-theme) לא עובד +לעברית. אל תסמוך על theme; השתמש ב-styles שמגדירים cs מפורש (Normal + +כל מה שיורש ממנו). + +## numbering definitions + +`word/numbering.xml` כולל ~22 abstractNum מוגדרים. הרלוונטי לנו: + +``` +numId=1 → abstractNumId=16 → numFmt=decimal, lvlText="%1." +``` + +יש גם hebrew1 (`numFmt=hebrew1` = א., ב., ג.) ב-`abstractNumId=0, 1`. +אף סגנון מוכן לא מפנה אליהם. אם תרצה בעתיד רשימה ממוספרת בעברית עם +Word auto-numbering — יש להזריק `numPr` ידנית עם `numId=29` (שמפנה +ל-abstractNumId=0). + +## גדלים — רפרנס מהיר + +| style | ascii | cs (עברית) | +|-------|-------|-----------| +| Normal | 13pt | 13pt | +| Heading 1 | 20pt | 18pt | +| Heading 2 | 13pt (ירושה) + bold + underline | אותו דבר | +| Title | 28pt (Aptos Display — לא עברית!) | — | +| List Paragraph | 14pt (David) | 14pt | +| Quote | 13pt (ירושה) + bold | 13pt | + +## טיפ + +כדי לבדוק את כל הסגנונות שבמסמך: + +```python +from docx import Document +d = Document("skills/docx/decision_template.docx") +for s in sorted(d.styles, key=lambda x: x.name): + print(s.type, s.name, s.style_id) +```