Spaces:
Sleeping
Sleeping
File size: 16,136 Bytes
e9e189e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 | # ApplyCRs Cookbook β Conditions for the Application to Work
This document lists the **preconditions** the input documents (CRs, TSs, the Excel
contribution list) and the **environment** must satisfy for the pipeline to apply CRs
correctly. Every rule here comes from a real failure we diagnosed and fixed. When a
CR doesn't apply cleanly, check it against this list *first* β most "bugs" are actually
an input that violates one of these conditions.
The pipeline is: `orchestrate_cr.py` β `fetch_crs.py` (parse Excel, download) β
`cr_parser.py` (read tracked changes) β `ts_applicator.py` (apply them) β
`finalize_ts.py` (title + Change History).
---
## 1. The Excel contribution list
The list is read by `parse_excel` / `parse_excel_all_accepted` in `fetch_crs.py`.
- The sheet must have columns whose headers include **`Status`**, **`Type`**, and a
submitter column (`SubmittedBy` / similar). Column detection is header-name based.
- Only rows with **`Status == "Accepted"`** and **`Type == CR`** are processed. Anything
else (Agreed, Revised, Noted, Withdrawn, discussion documents) is silently skipped β
by design.
- **Submitter filter:**
- `apply-cr` skill: pass the expert's name β only that person's CRs are processed.
- `apply-cr-loop` skill: pass an **empty string `""`** β *all* Accepted CRs, any author.
Never substitute a name here even if the user mentions one.
- Both `.xls` (via `xlrd`) and `.xlsx` are supported.
---
## 2. The CR document β cover page (how the CR identifies its target)
The target TS spec number and version are read from the CR's **cover table** by
`parse_cr_cover` and `extract_cr_metadata`. This is the single most important thing
the CR must get right β if the cover page can't be parsed, the CR is skipped entirely
with `WARNING: could not parse cover page`.
The cover table is located by scanning **all** tables for one containing the literal
text **`CHANGE REQUEST`** (also accepts `CR`, `CHANGE REQUEST`). Requirements:
- **Spec number** must appear as a cell in the form `NNN NNN` or `NNN NNN-N`
(e.g. `102 221`, `102 226-2`) β three digits, a single space, three digits. It is
found either as an exact-match cell, as the cell **immediately after** `CHANGE REQUEST`,
or as a substring inside a larger cell (fallback). A non-breaking space (`\xa0`) is
tolerated and normalised.
- **Current version** must appear in the cell **immediately after** a cell containing
`Current version:`. Accepted formats: `X.Y`, `X.Y.Z`, optionally `V`-prefixed
(e.g. `18.3.0`, `V18.3.0`). It is normalised to `X.Y.Z`.
- Other metadata read from the cover table for the Change History row: **Meeting #**,
**CR number**, **Rev**, **Category (Cat)**, and **Title of change**. The title is read
from the cell after a `Title` / `Title of change` label; the version from `Curr. vers` /
`Current version`.
**If the cover page uses a non-standard layout** (label and value not adjacent, spec
number split across cells, missing `CHANGE REQUEST` marker), parsing fails. Fix the CR
cover to the standard ETSI CR-form layout rather than patching the parser for one document.
---
## 3. The downloaded TS β title paragraph and version match
`download_ts` fetches the target TS and **verifies it before accepting it**:
- The first paragraph (title) must contain the spec number (space-insensitive).
- The first paragraph must contain **`V{version}`** exactly matching the cover page.
- The file must be a real DOCX (starts with `PK` zip magic).
Consequences you must know:
- **The ETSI portal sometimes returns the wrong version** (e.g. V17.4.0 when V17.3.0 was
requested). `download_ts` rejects a version mismatch. When the closest available version
is used deliberately, `update_title_para` falls back to the *actual* version found in the
title (via `re.search(r'V(\d+\.\d+\.\d+)', title_text)`) and logs a `NOTE`. Body changes
are still applied regardless of the version mismatch.
- **Some TS versions have no DOCX on the portal at all** (confirmed: TS 102 268 v7.3.0).
This is a permanent portal limitation β the CR cannot be applied and must be skipped.
### The TS title paragraph
For the title update (`finalize_ts.py`) to work, **`doc.paragraphs[0]`** must:
- Contain the version string as `V{old_version}`.
- Contain a publication date in the form **`(YYYY-MM)`**. If no `(YYYY-MM)` pattern is
present, the title update is skipped with `[Title] SKIP β no (YYYY-MM) pattern`
(this is logged, not an error β body changes still apply).
The new version is computed as **X.(Y+1).0** (`derive_new_version`) and the new date via
the 5-day rule (`compute_pub_date`): the next month if today is within 5 days of it,
otherwise the current month.
### The Change History table
`finalize_ts.py` inserts a new row into the **Change History** table, located by finding
a table whose first row contains the text `Change history` (the merged title cell).
- The table's column layout is auto-detected (standard `date|meeting|uid|cr|rev|cat|title|old_v|new_v`,
plus two known variants without `old_v` or without `date`).
- **Some TSs legitimately have no Change History table** (confirmed: TS 102 267 has only a
History/Annex table). The `[Change History] NOT PRESENT` log line is **expected and correct**
for those β not a bug.
---
## 3b. Multiple CRs on the same TS
This is a first-class, supported case β but it has rules.
**Grouping.** CRs are grouped by **`(spec_number, version)`** (orchestrator Step 3). Two
CRs that target the same spec *and* the same current version are put in one group and
applied together to a single TS. The log header shows `-- TS 102 221 v18.3.0 (2 CR(s): X, Y) --`.
**How a group is applied:**
- Each CR in the group is parsed independently, and all their changes are **concatenated
into one combined manifest** (in the order the UIDs appear in the group).
- The combined manifest is applied to the TS in a **single pass** β the CRs are *not*
applied to separate copies and merged afterwards.
- The version bump (**X.(Y+1).0**) and the title update happen **once** for the whole
group, not once per CR.
- **Each** participating CR adds its **own row** to the Change History table (one row per
CR, all showing the same oldβnew version).
- If one CR in the group fails to parse or download, the others still apply; the failure is
recorded per-UID and the group continues.
**β οΈ Order dependency β the main risk.** Because changes are applied sequentially in UID
order, one CR can invalidate another's anchor:
- If CR-A renames text (e.g. `SCP81` β `SCP8X`) and CR-B anchors on the original text, CR-B
will fail with `anchor not found` once CR-A has been applied β even though CR-B was valid
against the pristine TS. This is a **known, expected** interaction (see also Β§6), not a
pipeline bug. Verify against the *raw* TS XML before treating it as one.
- For the same reason, two CRs must not edit the **exact same run/paragraph** in conflicting
ways. If they do, the second edit acts on text the first already changed. Non-overlapping
changes (the normal case β different sections, different tables) combine cleanly.
**Same spec, different versions = separate groups.** If two CRs cite the same spec but
different `Current version` values, they are **different** `(spec, version)` keys β each is
applied to its own separately-downloaded TS, from its own base version. They are never
merged into a single output document. (This usually indicates the contribution list mixes
CRs written against different baselines; each produces its own `ts_..._was_v...docx`.)
---
## 4. How tracked changes must be made in the CR (the core rules)
The parser (`cr_parser.py`) recognises exactly **four** kinds of change. Understanding
the difference between *inline* and *section-level* changes is the single most important
concept β see the β οΈ box.
### 4a. Inline text change β `text_replace`
The paragraph stays; only words inside it are wrapped in `w:del` / `w:ins`.
Use this for editing text within a sentence, a cell, or a heading.
```xml
<w:p> <w:del><w:delText>old</w:delText></w:del> <w:ins><w:t>new</w:t></w:ins> </w:p>
```
- A **pure deletion** (del with no adjacent ins) is recorded as `old β ""`.
- Whitespace-only deletions with no adjacent insertion are discarded (Word artefacts).
- A `w:ins` that lives inside `w:rPr` is a **formatting change**, not content β ignored.
### 4b. Whole-paragraph / whole-table replacement β `section_replace`
> β οΈ **The critical distinction.** When the CR author deletes an entire heading + table
> block and types a new one, the change is tracked at the **structural** level: the
> paragraph-mark carries `w:del`/`w:ins` (inside `w:pPr/w:rPr`), and **every row** of a
> table carries `w:del`/`w:ins` in its `w:trPr`. The parser detects the pattern
> *del-block β (optional empty separator) β ins-block* and stores the raw XML of all those
> elements verbatim, so the applicator transplants them into the TS exactly like a Word
> copy-paste (fonts, cell widths, vMerge, styles, authorship all preserved).
>
> **Never** decompose a section replacement into individual `text_replace` / `row_insert`
> operations. Doing so causes: wrong-table matches, lookup poisoning, reversed row order,
> and a visually wrong result. This is enforced as a hard rule.
So, for a CR author: to replace a table, **delete the whole old table and paragraph and
insert the whole new one** (Word does this naturally when you select-and-retype with
track-changes on). Do *not* hand-edit individual cells if the intent is a full rewrite.
### 4c. New table row β `row_insert`
A row whose `w:trPr` contains `w:ins`, inside an otherwise-stable table. The parser records
the cell text/width/style/vMerge and anchors the new row after the nearest preceding
**stable** row (by its column-0 text).
### 4d. Brand-new paragraph β `para_insert`
A wholly-new paragraph (`w:pPr/w:rPr` has `w:ins`) with no corresponding deletion. Anchored
after the previous stable paragraph.
---
## 5. Headings and section numbering in the CR
Anchoring is **section-aware**: every change is stamped with its `section_number`, and the
applicator restricts its search to that section of the TS. For this to work:
- A CR heading must use a real Word **`Heading N` paragraph style** *and* start with a
dotted number (`8.6`, `11.1.22.3.1`). Both are required:
- Text starting with a digit but **not** a heading style (e.g. a bit-description line
`1 = SMS packingβ¦` in style `B30`) is correctly **ignored** β it will not hijack the
current section.
- ETSI stores the number and title in separate runs joined by a `<w:tab/>`
(`<w:t>8.12</w:t><w:tab/><w:t>Result</w:t>`). The parser reconstructs this with the tab,
so the heading regex matches. Don't collapse the tab into the number in the CR.
- On the TS side, the **Table of Contents** entries have the same text as real headings but
use `toc N` styles β they are excluded from section detection by the same heading-style
requirement. Don't worry about the TOC.
---
## 6. How anchors are matched (why "anchor not found" happens)
`ts_applicator.py` finds the target paragraph with **5-tier matching** (highest confidence
first): exact substring β NBSP-normalised β whitespace-normalised β original-text
(reconstruct pre-change text, skipping `w:ins`) β full-text (del + ins concatenated).
Matches below confidence 0.8 log a `WARN`.
For the anchor text in a CR to be found, it must exist **as readable text** in the target
TS section. Failures and their meaning:
- **`WARN section 'X' not found`** β the declared section isn't in this TS; falls back to a
global search. Usually fine.
- **`ERROR ... anchor declared in section X but found only in section Y`** β the anchor text
exists but in the wrong place. Often means the CR's section number is wrong, or two CRs in
the batch renamed the anchor (see below).
- **`ERROR ... anchor not found in TS`** β the text genuinely isn't there. Common real causes:
- The anchored content is a **figure, image, or screenshot** β there is no text to match.
These changes **cannot be automated** and are acceptable skips.
- The content lives in a **zip-embedded archive**, not inline paragraphs (confirmed: the
Java API in TS 102 267 Annex A). Any CR anchoring on a Java method signature there will
always fail. Verify with `zipfile` + `re` before spending fix attempts.
- **Cross-CR anchor renaming:** if CR-A renames `SCP81` β `SCP8X` and CR-B (same batch)
anchors on the original `SCP81β¦`, the anchor no longer exists after CR-A applied. Check
the raw XML before treating it as a bug.
Special case: a very short `old` text (len < 4, e.g. `'1'`) is ambiguous. When a
`para_context` is available it is used to locate the paragraph first, then `old` is verified
inside it β bypassing false matches on titles.
---
## 7. Terminal Profile tables (TS 102 223) β special rule
TS 102 223 has 40+ Terminal Profile byte tables that **all share the same empty header**
(`['', '', '']` with `b8`/`b7`β¦ labels). `_find_table` matches by header, so `'' in anything`
is always true β it returns the **first** table every time, and a tracked deletion of a
`b8` label poisons subsequent lookups.
- **Never** use `_find_table` for these. Always use `_find_table_by_section(doc, "Thirty
fifth byte:")`, which walks to the heading paragraph and returns the table right after it.
- This is why a full byte-table rewrite must be a **`section_replace`** (Β§4b), not scattered
cell edits.
---
## 8. Formatting rules β the CR is the only authority
When inserting new content, take the formatting from the **CR text exactly as written**;
never copy NOTE/bullet/indent conventions from the TS.
- If the CR writes `NOTE: text` (space after colon), use a space β not a tab.
- Preserve leading/trailing spaces and tabs by setting
`xml:space="preserve"` on the `<w:t>`, or Word silently trims them.
- Use the internal style **`w:val`** (e.g. `B1`, `Heading5`) when building XML, not the
python-docx style **name** (`B1+`, `Heading 5`) β they differ. Use `get_style_val()`.
- `modify_para_text()` collapses all runs into one and loses per-run formatting β only use
it on uniformly-formatted paragraphs. The `section_replace` path clones runs verbatim and
keeps mixed formatting.
---
## 9. Insertion order and index shifting (implementation invariants)
- **Insertion order trap:** repeated `ref.addnext(x)` on the same reference reverses order.
Advance the pointer after each insert (`ref = new_element`) or use
`insert_paras_after()` / `insert_rows_after()` from `docx_helpers.py`.
- **Index shifting:** paragraph/row indices change on every insert or delete. Collect all
needed object references *first*, then mutate; process higher-index sections before lower.
---
## 10. Environment and operational conditions
- **Proxy** must be set before any network call:
`export http_proxy="http://185.46.212.90:80" https_proxy="http://185.46.212.90:80"`
- **Windows paths** must be converted to WSL form (`C:\...` β `/mnt/c/...`) before passing
to any script (`wsl_path` / `to_wsl_path`).
- **File locked by Word:** a `PermissionError` on save means the output DOCX is open in Word.
Save to `<name>_tmp.docx`, ask the user to close Word, then rename.
- **Always verify after save:** re-open the output and confirm section paragraph counts,
text, order (especially bullets/rows), and that no stray empty paragraphs were introduced.
---
## 11. What is *not* a bug (acceptable skips)
A `SKIP` or `ERROR` line is acceptable **only** when the log shows the change is:
- a **figure, image, or screenshot** (no text to anchor/apply),
- a **formatting-only** change, or
- content in a **portal-unavailable TS version** or a **zip-embedded archive**.
Everything else β a text/table/row change that didn't apply β is a real failure and should
be traced back to one of the conditions above (most often Β§2 cover page, Β§4 change type, or
Β§6 anchor text).
|