|
| 1 | +# text-operations Specification |
| 2 | + |
| 3 | +## Purpose |
| 4 | +TBD - created by archiving change add-cross-boundary-text-operations. Update Purpose after archive. |
| 5 | +## Requirements |
| 6 | +### Requirement: Virtual Text Map |
| 7 | + |
| 8 | +The system SHALL provide a flattened text view of document content that maps character positions back to their source XML elements. |
| 9 | + |
| 10 | +The text map SHALL: |
| 11 | +- Concatenate all visible text from `<w:t>` elements within a paragraph |
| 12 | +- Exclude text inside `<w:delText>` elements (deleted content) |
| 13 | +- Track whether each character position is inside a `<w:ins>` or `<w:del>` element |
| 14 | +- Map each character position to its source `<w:t>` node and offset |
| 15 | + |
| 16 | +#### Scenario: Build text map for paragraph with tracked changes |
| 17 | + |
| 18 | +- **GIVEN** a paragraph containing: regular text "Hello ", inserted text "beautiful ", regular text "world" |
| 19 | +- **WHEN** `build_text_map()` is called on the paragraph |
| 20 | +- **THEN** the text map contains "Hello beautiful world" |
| 21 | +- **AND** positions 0-5 map to the first `<w:t>` with `is_inside_ins=False` |
| 22 | +- **AND** positions 6-15 map to the second `<w:t>` with `is_inside_ins=True` |
| 23 | +- **AND** positions 16-20 map to the third `<w:t>` with `is_inside_ins=False` |
| 24 | + |
| 25 | +#### Scenario: Deleted text excluded from visible text |
| 26 | + |
| 27 | +- **GIVEN** a paragraph containing: "Hello " and deleted text "old " and "world" |
| 28 | +- **WHEN** `build_text_map()` is called |
| 29 | +- **THEN** the text map contains "Hello world" (deleted text excluded) |
| 30 | + |
| 31 | +### Requirement: Cross-Boundary Text Search |
| 32 | + |
| 33 | +The system SHALL find text that spans multiple XML elements within a paragraph. |
| 34 | + |
| 35 | +#### Scenario: Search finds text spanning element boundary |
| 36 | + |
| 37 | +- **GIVEN** a paragraph with "Exploratory Aim: " in one `<w:t>` and "To examine" in another |
| 38 | +- **WHEN** searching for "Aim: To" |
| 39 | +- **THEN** the search succeeds and returns match information |
| 40 | +- **AND** the match indicates it spans multiple elements |
| 41 | + |
| 42 | +#### Scenario: Search finds text spanning insertion boundary |
| 43 | + |
| 44 | +- **GIVEN** a paragraph with "Hello " as regular text and "world" inside `<w:ins>` |
| 45 | +- **WHEN** searching for "Hello world" |
| 46 | +- **THEN** the search succeeds |
| 47 | +- **AND** the match indicates it spans a revision boundary |
| 48 | + |
| 49 | +### Requirement: Visible Text API |
| 50 | + |
| 51 | +The system SHALL provide a public API to retrieve the flattened visible text of a document. |
| 52 | + |
| 53 | +#### Scenario: Get visible text from document |
| 54 | + |
| 55 | +- **GIVEN** a document with paragraphs containing mixed regular and tracked-change content |
| 56 | +- **WHEN** `get_visible_text()` is called |
| 57 | +- **THEN** the method returns a string containing all visible text |
| 58 | +- **AND** deleted text is excluded |
| 59 | +- **AND** inserted text is included |
| 60 | + |
| 61 | +### Requirement: Boundary-Aware Text Replacement |
| 62 | + |
| 63 | +The system SHALL replace text that spans multiple `<w:t>` elements within the same revision context, using proper node splitting. |
| 64 | + |
| 65 | +#### Scenario: Replace text spanning multiple runs |
| 66 | + |
| 67 | +- **GIVEN** a paragraph with "Hello " in one run and "world" in another (no revisions) |
| 68 | +- **WHEN** `replace_text("Hello world", "Hi there")` is called |
| 69 | +- **THEN** the replacement succeeds |
| 70 | +- **AND** the original text is wrapped in `<w:del>` |
| 71 | +- **AND** the new text is wrapped in `<w:ins>` |
| 72 | + |
| 73 | +### Requirement: Mixed-State Editing |
| 74 | + |
| 75 | +The system SHALL replace text that spans revision boundaries by decomposing the operation into per-segment atomic actions, one for each revision context (regular text, inside `<w:ins>`, inside `<w:del>`). |
| 76 | + |
| 77 | +#### Scenario: Replace spanning regular text and insertion |
| 78 | + |
| 79 | +- **GIVEN** a paragraph with "Exploratory Aim: " as regular text and "To examine" inside `<w:ins>` |
| 80 | +- **WHEN** `replace_text("Aim: To", "Goal: To")` is called |
| 81 | +- **THEN** the replacement succeeds |
| 82 | +- **AND** "Aim: " is wrapped in `<w:del>` (standard deletion of regular text) |
| 83 | +- **AND** "To" is removed from the `<w:ins>` element (undoing partial insertion) |
| 84 | +- **AND** "Goal: To" is inserted as a new `<w:ins>` element |
| 85 | +- **AND** the remaining " examine" stays inside the original `<w:ins>` |
| 86 | + |
| 87 | +#### Scenario: Replace fully within insertion |
| 88 | + |
| 89 | +- **GIVEN** a paragraph with "Hello beautiful world" entirely inside `<w:ins>` |
| 90 | +- **WHEN** `replace_text("beautiful", "wonderful")` is called |
| 91 | +- **THEN** the replacement succeeds |
| 92 | +- **AND** "beautiful" is removed from the insertion (undoing partial insertion) |
| 93 | +- **AND** "wonderful" is inserted as a new `<w:ins>` element |
| 94 | + |
| 95 | +#### Scenario: Insertion node is split when partially matched |
| 96 | + |
| 97 | +- **GIVEN** a paragraph with `<w:ins>To examine whether</w:ins>` |
| 98 | +- **WHEN** a delete operation targets "To" (the first 2 characters) |
| 99 | +- **THEN** the `<w:ins>` is split into two parts |
| 100 | +- **AND** the `<w:ins>To</w:ins>` portion is removed |
| 101 | +- **AND** `<w:ins> examine whether</w:ins>` remains intact |
| 102 | + |
0 commit comments